I am using MockBean in a set of Controller WebMvcTests. The bean in question is a session scoped bean and is effectively a DTO with a few methods hanging off it used in the controller.
@Bean
@SessionScope
public MyBeanDTO bean() {
return new MyBeanDTO();
}
The controller is thus:
@Controller
public class MyController {
@Autowired
private MyBeanDTO myBean;
//mappings which set/get values/invoke methods of the bean
}
I've written some unit tests using WebMvcTest. There are about a dozen tests. Most of them involve getting a value from the bean, or checking something matches by calling a method on the bean, or both. As such there are mocks for the methods of the bean, like getValue or matches(value):
@WebMvcTest(MyController.class)
@Import({ARealValidator.class,SecurityConfig.class})
class MyControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private MyBeanDTO myBean;
@MockBean
private IMyService myService;
private UUID token = UUID.randomUUID();
@BeforeEach
void init() {
when(myBean.getValue()).thenReturn(token);
}
@Test
void testA() throws Exception {
when(myService.findByToken(token)).thenReturn(new MyObject());
mockMvc.perform(get("/some/request"))
.andExpect(view().name("some/view"));
}
@Test
void testB() throws Exception {
when(myBean.matches(token)).thenReturn(false);
mockMvc.perform(get("/another/request?val="+token.toString()))
.andExpect(redirectedUrl("invalid"));
}
}
When ran together, most of the tests pass - but some fail, and it looks as though the mock has somehow been bypassed, as adding a line to log myBean.toString() returns myBean bean for the passing tests, and MyBean(value=null, otherValue=null) for the failing tests.
Running each test individually passes the tests, and adding @DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) allows them to pass ran as a suite (albeit slowly).
The behaviour of the MockBean toString suggests the mock isn't being consistently reset/applied - does this seem like an issue to you?
Comment From: mbhave
This seems related to #17817.
@fcol88 There is a note in the reference documentation for working around this
CGLib proxies, such as those created for scoped beans, declare the proxied methods as final. This stops Mockito from functioning correctly as it cannot mock or spy on final methods in its default configuration. If you want to mock or spy on such a bean, configure Mockito to use its inline mock maker by adding org.mockito:mockito-inline to your application’s test dependencies. This allows Mockito to mock and spy on final methods.
Can you let us know if that fixes your issue?
Comment From: fcol88
Hi Madhura, thankyou for your quick reply!
I thought so too, I had seen that mentioned as a previous issue, and noted that removing @SessionScope would fix the issue, so as a quick diagnostic (before importing mockito-inline) I removed the scope just to see, but it didn't work in my case, which was odd, and I ruled it out as the issue.
I've added mockito-inline on your recommendation (both overriding the managed version of 4.0 and specifying 4.4.0) and the result is the same - from what I've understood, adding it as a dependency should in theory be enough to have it function correctly?
Additionally, printing toString() prior to the test run, the bean shows as being mocked successfully, it's only upon MockMvc performing a request that the "real" bean is being used.
Apologies, I was really hoping it would be as simple as that!
Comment From: mbhave
@fcol88 Thanks for the feedback. Can you provide a minimal sample that we can run to reproduce the issue? You can attach the sample to this issue as a zip or provide a link to a separate Github repository.
Comment From: fcol88
Hi Madhura, very sorry for the delay - I've tried to reproduce the error in a "clean" project, but I've not been able to. I've attached a cut-down version of what I've written. The mechanism is nigh-on identical as the test project I created, but the mock is seemingly only used for the first test.
Comment From: fcol88
I've cut it down further - it looks like it can be replicated with as few as three tests - I wondered whether it was a scale issue but apparently not.
Comment From: mbhave
@fcol88 It happens due to syncSession = new SyncRequestSessionDTO(); in the notFound() method in PrescriptionSyncController. Running the test which causes the notFound to be invoked first will overwrite the mock that's been created. You can see that it works by commenting that line out.
Comment From: fcol88
Oh my god, I'm so sorry! What an idiot I am. Thankyou for your help!