'Why is MockBean not working in an integrationtest with RabbitMQ?

I have a Spring Boot application with RabbitMQ and I try to test my code in an integration test. My integration test does work in some cases, but not in all cases.

Application code

@Component
@AllArgsConstructor
public class TestQueueListener {

  private final TestService testService;

  @RabbitListener(queues = "q_test")
  public void listen(String value) {
    testService.doSomething(value);
  }
}
@Service
@Slf4j
public class TestService {

  public void doSomething(String value) {
    log.info(value);
  }
}

Test code

@TestClassOrder(ClassOrderer.OrderAnnotation.class)
class Tests {

  @Nested
  @Order(1)
  @SpringBootTest(webEnvironment = WebEnvironment.NONE)
  @ExtendWith(OutputCaptureExtension.class)
  class ServiceTest {

    @Autowired TestService testService;

    @Test
    void testDoSomething(CapturedOutput output) {
      testService.doSomething("ServiceTestValue");
      assertTrue(output.toString().contains("ServiceTestValue"));
    }
  }

  @Nested
  @Order(2)
  @SpringBootTest(webEnvironment = WebEnvironment.NONE)
  class ListenerTest {

    @Autowired private RabbitTemplate rabbitTemplate;
    @MockBean private TestService testService;

    @Test
    void testListen() {
      rabbitTemplate.convertAndSend("q_test", "testValue");
      verify(testService, timeout(30000)).doSomething("testValue");
    }
  }
}

Logs

2022-05-19 10:59:21.527  INFO 102 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Neither @ContextConfiguration nor @ContextHierarchy found for test class [test.Tests$ListenerTest], using SpringBootContextLoader
2022-05-19 10:59:21.528  INFO 102 --- [           main] o.s.t.c.support.AbstractContextLoader    : Could not detect default resource locations for test class [test.Tests$ListenerTest]: no resource found for suffixes {-context.xml, Context.groovy}.
2022-05-19 10:59:21.529  INFO 102 --- [           main] t.c.s.AnnotationConfigContextLoaderUtils : Could not detect default configuration classes for test class [test.Tests$ListenerTest]: ListenerTest does not declare any static, non-private, non-final, nested classes annotated with @Configuration.
2022-05-19 10:59:21.534  INFO 102 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Found @SpringBootConfiguration test.TestApplication for test class test.Tests$ListenerTest
2022-05-19 10:59:21.536  INFO 102 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener, org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.event.ApplicationEventsTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
2022-05-19 10:59:21.536  INFO 102 --- [           main] .b.t.c.SpringBootTestContextBootstrapper : Using TestExecutionListeners: [org.springframework.test.context.web.ServletTestExecutionListener@7130e7a8, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@3fe79b11, org.springframework.test.context.event.ApplicationEventsTestExecutionListener@15f209fc, org.springframework.boot.test.mock.mockito.MockitoTestExecutionListener@4f2eec7c, org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener@39d55f96, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3ae974ae, org.springframework.test.context.transaction.TransactionalTestExecutionListener@3fd127e6, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@724716c, org.springframework.test.context.event.EventPublishingTestExecutionListener@149eb7f1, org.springframework.boot.test.mock.mockito.ResetMocksTestExecutionListener@65a3edc8, org.springframework.boot.test.autoconfigure.restdocs.RestDocsTestExecutionListener@537b088d, org.springframework.boot.test.autoconfigure.web.client.MockRestServiceServerResetTestExecutionListener@77f0ac9f, org.springframework.boot.test.autoconfigure.web.servlet.MockMvcPrintOnlyOnFailureTestExecutionListener@16b0acaf, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverTestExecutionListener@6c87de1c, org.springframework.boot.test.autoconfigure.webservices.client.MockWebServiceServerTestExecutionListener@67b38c61]
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
:: Spring Boot ::                (v2.6.6)
2022-05-19 10:59:21.634  INFO 102 --- [           main] test.Tests$ListenerTest                  : Starting Tests.ListenerTest using Java 11.0.10 on runner-grawsgkm-project-663-concurrent-0 with PID 102 (started by root in /builds/test/test-app)
2022-05-19 10:59:21.635  INFO 102 --- [           main] test.Tests$ListenerTest                  : No active profile set, falling back to 1 default profile: "default"
2022-05-19 10:59:22.307  INFO 102 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: [tmprabbit:5672]
2022-05-19 10:59:22.317  INFO 102 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Created new connection: rabbitConnectionFactory#4813cd48:0/SimpleConnection@662f5d78 [delegate=amqp://[email protected]:5672/, localPort= 49876]
2022-05-19 10:59:22.336  INFO 102 --- [           main] test.Tests$ListenerTest                  : Started Tests.ListenerTest in 0.796 seconds (JVM running for 7.761)
2022-05-19 10:59:22.383  INFO 102 --- [ntContainer#0-1] test.TestService                         : testValue
[ERROR] Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 36.706 s <<< FAILURE! - in test.Tests
[ERROR] testListen  Time elapsed: 30.053 s  <<< FAILURE!
org.mockito.exceptions.verification.WantedButNotInvoked: 
Wanted but not invoked:
testService bean.doSomething("testValue");
-> at test.Tests$ListenerTest.testListen(Tests.java:46)
Actually, there were zero interactions with this mock.
    at test.Tests$ListenerTest.testListen(Tests.java:46)

Research

The logs show that the real TestService is called, not the mocked one. I thought it has something to do with Spring's context cache, so I enabled DEBUG logs. But apparently there are the right number of contexts in the cache. In extended logs I can also see that the right context is retrieved.

2022-05-19 11:29:42.707 DEBUG 103 --- [           main] c.DefaultCacheAwareContextLoaderDelegate : Storing ApplicationContext [1261461477] in cache under key [[MergedContextConfiguration@3f7e3d48 testClass = Tests.ListenerTest, locations = '{}', classes = '{class test.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@31b82e0f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@10272bbb, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@769c78c2, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@da28d03, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@65afeb6d, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@b27b210, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@7c2b6087], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]
2022-05-19 11:29:42.708 DEBUG 103 --- [           main] org.springframework.test.context.cache   : Spring test ApplicationContext cache statistics: [DefaultContextCache@2c36de3b size = 2, maxSize = 32, parentContextCount = 0, hitCount = 11, missCount = 2]
2022-05-19 11:29:42.712 DEBUG 103 --- [           main] c.DefaultCacheAwareContextLoaderDelegate : Retrieved ApplicationContext [1261461477] from cache with key [[MergedContextConfiguration@3f7e3d48 testClass = Tests.ListenerTest, locations = '{}', classes = '{class test.TestApplication}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true}', contextCustomizers = set[org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@31b82e0f, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@10272bbb, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@769c78c2, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@da28d03, org.springframework.boot.test.autoconfigure.actuate.metrics.MetricsExportContextCustomizerFactory$DisableMetricExportContextCustomizer@65afeb6d, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@b27b210, org.springframework.boot.test.context.SpringBootTestArgs@1, org.springframework.boot.test.context.SpringBootTestWebEnvironment@7c2b6087], contextLoader = 'org.springframework.boot.test.context.SpringBootContextLoader', parent = [null]]]

I also added following log statements to see if the used TestService is really a mock. The result was that both variables contain a mock.

log.info("Direct: " + Mockito.mockingDetails(testService).isMock());
log.info("Indirect: " + Mockito.mockingDetails(testQueueListener.testService).isMock());
  • If I change the order of the tests, all tests pass. However, I don't want to order all my tests as nested classes within one class. That is not readable.

  • If I call TestQueueListener direcly without RabbitMQ, all tests pass.

    @Nested
    @Order(2)
    @SpringBootTest(webEnvironment = WebEnvironment.NONE)
    class ListenerTest {
    
      @Autowired private TestQueueListener testQueueListener;
      @MockBean private TestService testService;
    
      @Test
      void testListen() {
        testQueueListener.listen("testValue");
        verify(testService, timeout(30000)).doSomething("testValue");
      }
    }
    

    But I also want to test messaging with RabbitMQ.

Question

Why is my integration test failing?



Solution 1:[1]

This works with a minimal winforms application in my system:

        System.Diagnostics.Process p = System.Diagnostics.Process.Start("notepad.exe");
        Application.Exit();

but also this:

        ExecuteAsAdmin("notepad.exe");
        Application.Exit()

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 phoebus