Search code examples
javaspringspring-bootbitbucket-pipelinesspring-mvc-test

Spring Boot Test with @WebMvcTest fails on Atlassian Bitbucket Pipeline


When running some @WebMvcTests locally, I do not have problems (Spring Boot 1.5.8, gradle 4.6):

@RunWith(SpringRunner.class)
@WebMvcTest(VZNFCController.class)
public class VZNFCControllerTest {

  private VZNFCTagAction action1 = new VZNFCTagAction();
  private VZNFCTagAction action2 = new VZNFCTagAction();

  @Before
  public void setUp(){
      action1.setMessage("message1");
      action2.setMessage("message2");
  }


  @Autowired
  private MockMvc mvc;

  @MockBean
  private VZNFCTagActionRepository actionRepository;

  @MockBean
  private MappingMongoConverter mongoConverter;

  @Test
  @WithMockUser
  public void testGetTagList() throws Exception {

    given(this.actionRepository.findAll())
              .willReturn(Arrays.asList(action1, action2));  
    this.mvc.perform(get("/api/nfc/tags")
            .accept(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(status().isOk());
    }
}

However, when I upload to Atlassian Bitbucket and run ./gradlew test --stacktrace there, I get the following:

java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117)
at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.

Now Bibucket pipelines use a Docker Image (java:8). When I switch back locally to @SpringBoot and @AutoConfigureMockMvc I get the same error on both environments. Same DB setup on both environments (same docker image for MongoDB), same everything... Could it be some port does not get mapped when using Docker? I think I do create Servlet requests...

EDIT

Emulating a Bitbucket pipeline build in a Docker container (as suggested here), it seems that mocking out the MappingMongoConverter and moving to @SpringBootTest together with @AutoConfigureMockMvc is enough to get it running. So @WebMvcTest with only partially mocked out context is enough without a container, but it will fail inside a Docker container such as the one present when using Bitbucket. Why?


Solution

  • Turns out there were some key beans missing, since the @WebMvc annotation will not pick up @Components, only what's needed for the web stack not including Repositories, but the issue was my security configuration which I want tested as well and which I now simply @Import (along with some other beans that the controller depends on):

    @RunWith(SpringRunner.class)
    @WebMvcTest(value = VZNFCController.class)
    @Import(value = {VZApiSecurityConfiguration.class, 
                     VZJwtTokenUtils.class, VZProperties.class})
    public class VZNFCControllerTest {
    
      @Autowired
      private MockMvc mvc;
    
      @MockBean
      private VZNFCTagService tagService;
    
      /* same as the above... I don't mock out the repository any more */
    
    }
    

    Lesson learned here is that all the autoconfigured test contexts (@WebMvc, @DataMongoTest, etc. ) speed up your tests (which is good, because I am paying build minutes on bitbucket). But you need to really know what's needed to get that slice of your application running. It forced me to really only mock the service to concentrate on the controller and then write some more tests for the DAO part of my application. Which is a good thing I guess.