Search code examples
spring-bootgradlejunitmockitoproject-reactor

Mocking is sometimes not applied when running a multi-class testsuite


I am testing a service which heavily relies on project reactor.

For many tests I am mocking the return value of the component responsible for API calls. The tests are split over multiple files. When I run the tests of one file, they are green, but when I execute all of the test files at once, some tests fail, with the error message indicating that the mocking did not succeed (Either the injected component returned null, or the implementation of the actual component is invoked). In the logs, there is no information about the mocking failing.

A code example:

interface API {
  Flux<Bird> getBirds();
}

@Component
class BirdWatcher {
  API api;
  BirdWatcher(API api) {
    this.api = api;
  }
  
  Flux<Bird> getUncommonBirds() {
    return api.getBirds()           // Although this is mocked in the test, in some runs it returns `null` or calls the implementation of the actual component
      .filter(Bird::isUncommon);
  }
}

@SpringBootTest
class BirdWatcherTests {
  @Autowired
  BirdWatcher birdWatcher;
  @MockBean
  API api;
  
  @Test
  void findsUncommonBirds() {
    // Assemble
    Bird birdCommon = new Bird("Sparrow", "common");
    Bird birdUncommon = new Bird("Parrot", "uncommon");
    Mockito.when(api.getBirds()).thenReturn(Flux.just(birdCommon, birdUncommon));
    
    // Act
    Flux<Bird> uncommonBirds = birdWatcher.getUncommonBirds();
    
    // Assert
    assertThat(uncommonBirds.collectList().block().size(), equalTo(1));
  }
}

For me the issue seems like a race condition, but I don't know where and how this might happen, and how I can check and fix this.

I am using spring-boot-test:2.7.8, pulling in org.mockito:mockito-core:4.5.1 org.mockito:mockito-junit-jupiter:4.5.1, and org.junit.jupiter:junit-jupiter:5.8.2, with gradle 7.8. For reactor, spring-boot-starter-webflux:2.7.8, depending on reactor:2.7.8.


Solution

  • I don't really know what the problem was, but I fixed this by annotating each class with @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)