I'm having trouble running my tests, when using the @MockBean annotation to mock a behavior, Spring reloads the context. As the example below:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = WalletPaymentApplication.class, properties = { "spring.cloud.config.enabled:true",
        "management.security.enabled=false" })
@RabbitListenerTest
@Transactional
@ActiveProfiles("test")
public abstract class WalletPaymentApplicationTests {

    public static final String JSON_MEDIA_TYPE = "application/json;charset=UTF-8";

    @Autowired
    protected EntityManager em;

    @MockBean
        protected RestTemplate template;

    protected MockMvc mockMvc;

    @Autowired
    protected WebApplicationContext wac;

    @SpyBean
    protected DomainEventPublisher domainEventPublisher;
}
public class PaymentControllerTest extends WalletPaymentApplicationTests {

    @MockBean
    private PaymentService service;

    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
    }

    @Test
    public void newPayment() throws Exception {

        when(service.newPayment(any(PaymentCommand.class)))
                .thenReturn(CompletableFuture.supplyAsync(() -> new PaymentResumeData(any(MerchantData.class),
                        any(CashbackData.class), any(BigDecimal.class), anyListOf(SummaryCardData.class))));

        String payload = "{\"paymentId\": \"4548888888888888\","
                + "\"customerId\": \"100\","
                + "\"merchantSubKey\": \"4df4447hjh8g-4d4g5f-vdgfg\"," 
                + "\"amount\": 650.00,"
                + "\"taxId\": \"frfr6-d4g4v7-4b8f\"}";

        MockHttpServletRequestBuilder builder = post("/payment").contentType(MediaType.parseMediaType(JSON_MEDIA_TYPE))
                .content(payload);

        mockMvc.perform(builder).andExpect(request().asyncStarted()).andExpect(status().isOk());
}
public class PaymentServiceTest extends WalletPaymentApplicationTests {

    @Test
    public void newPayment() throws Exception {
        String date = formatarData(new Date(), "yyyy-MM-dd HH:mm:ss");
        when(template.getForObject(configureUrl(customerUrl), CustomerRows.class, "100"))
                .thenReturn(fixture.fixtureCustomer());
        when(template.getForObject(merchantUrl, MerchantRows.class, "wevf-f5vgb-5f", date))
                .thenReturn(fixture.fixtureMerchant());
        when(template.getForObject(configureUrl(cardUrl), CardRows.class, "100"))
                .thenReturn(fixture.fixtureCard());

        PaymentResumeData resume = service.newPayment(new PaymentCommand("12378000000000000", "100",
                "wevf-f5vgb-5f", 3000L, "85640827807")).get();

        Payment payment = repository.getPaymentById(new PaymentId("12378000000000000"))
                .orElseThrow(() -> new PaymentNotFoundException());

        assertEquals(BigDecimal.valueOf(150.60), resume.getBalance());
        assertEquals("promocode", resume.getCashback().getType());
}

For test scenario, the spring recharges the context 3 times. If i remove the annotation, spring starts the context only once

Comment From: philwebb

The Spring test framework will cache an ApplicationContext whenever possible between test runs. In order to be cached, the context must have an exactly equivalent configuration. Whenever you use @MockBean, you are by definition changing the context configuration.

The PaymentServiceTest extends WalletPaymentApplicationTests and inherits a @MockBean of RestTemplate (the config is WalletPaymentApplication + mock RestTemplate).

The PaymentControllerTest also extends WalletPaymentApplicationTests, but it defines an additional PaymentService @MockBean (the config is WalletPaymentApplication + mock RestTemplate + mock PaymentService).

This additional mock means that the same context can't be cached. In PaymentServiceTest the PaymentService is real, in PaymentControllerTest it's a mock. The two contexts contain different beans.

Comment From: eutiagocosta

I understood, how could i mock the external calls then in my real tests with the restTemplate, without the spring reloading the context?

Comment From: philwebb

I generally use MockRestServiceServer or WireMock for that kind of thing. I gave a talk that covered some testing options at Spring One last year that might be helpful. You can watch a recording here.

Comment From: SeriyBg

@eutiagocosta @philwebb I had a similar issue, and I have overcome it by doing the following. I'm having in the test sources a separate configuration for bean mocking:

@Configration
public class MockConfig {

  @Bean
  public ExternalService externalService() {
    return mock(ExternalService.class);
  }
}

And in the test class I'm just using @Autowire for the ExternalService:

@Autowire
private ExternalService externalService;

This is the workaround, but it does the job for me.

Comment From: surapuramakhil

@SeriyBg your workaround - mocking will be happening for all test classes right? How can we do this only for that test class ?

Comment From: prp28

@surapuramakhil Yes, for only particular test class, you can create mock object and inject it on demand by using ReflectionTestutils in test class.

ExternalService externalService = Mockito.mock(ExternalService.class);

@Test
void testMockBeanWorksWithInjection (){
ReflectionTestUtils.setField(<AutowiredClassWhichHasMockField>, "externalService", externalService);
// Rest of test code
}