Home > Enterprise >  Spring Boot @WebMvcTest having @MockBean null - using SpringRunner works
Spring Boot @WebMvcTest having @MockBean null - using SpringRunner works

Time:01-21

short: there are difficulties in migrating to JUnit5 from Junit4. After removing @RunWith(SpringRunner.class) my @MockBean annotated service is null, although I am using `@WebMvcTest'. Why is it not working?

long: I am struggling with that since several days. I am having some tests for my Spring Boot application which worked fine using @RunWith(SpringRunner.class) im combination with @WebMvcTest.

Since that is JUnit4 I was trying to migrate to Junit5. I removed the vintage dependecy from the pom and added the exclusion for spring-boot-starter-test. There is just the junit-jupiter dependency left for JUnit 5.

I read some posts and samples about that. Since I dont want to load the full context, SpringBootTest was not the way (and does not work at all) I 'think' I found out that @WebMvcTest is enough since it already contains the @extendWith annotation which is the JUnit5 equivalent for @RunWith(SpringRunner.class). So: I would just have to remove the springRunner and it should work.

But after deleting that single line my with @MockBean annotated service is null from now on. I can find many samples just using @WebMvcTest and @MockBean. So I dont understand, why mine is not working. Adding @RunWith(SpringRunner.class) makes it working again.

Thats the test class (without springrunner):

package my.package;

import com.fasterxml.jackson.databind.ObjectMapper;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileController;
import my.package.controller.SkillTileResponse;
import my.package.service.SkillTileService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@WebMvcTest(SkillTileController.class)
public class SkillTileControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private SkillTileService skillTileService;

    private final SkillTileTestDataFactory testDataFactory = new SkillTileTestDataFactory();

    @Test
    public void givenSkillTiles_whenGETFindAll_thenReturnJsonArray() throws Exception {

        List<SkillTileResponse> skillTiles = testDataFactory.createTestSkillTileResponses(1, true, null, true);
        SkillTileResponse st1 = skillTiles.get(0);

        given(skillTileService.findAll(null)).willReturn(skillTiles);

        mvc.perform(get("/skill-tile/")
                        .contentType(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$", hasSize(1)))
                .andExpect(jsonPath("$[0].name").value(st1.getName()));
    }
}

Instead using Spring runner works like that:

package my.package;

import com.fasterxml.jackson.databind.ObjectMapper;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileController;
import my.package.controller.SkillTileResponse;
import my.package.service.SkillTileService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import java.util.List;

import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@WebMvcTest(SkillTileController.class)
public class SkillTileControllerIntegrationTest {

    @Autowired
    private MockMvc mvc;

    @MockBean
    private SkillTileService skillTileService;
[...]
}

May the head of the service is also of interest:

package my.package.service;

import lombok.RequiredArgsConstructor;
import my.package.controller.SkillTileCommand;
import my.package.controller.SkillTileResponse;
import my.package.exception.ResourceNotFoundException;
import my.package.repository.SkillTileEntity;
import my.package.repository.SkillTileRepository;
import my.package.utils.ObjectMapper;
import my.package.utils.ResourceUtility;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@RequiredArgsConstructor
@Service
public class SkillTileService {

    private final SkillTileRepository skillTileRepository;
[...]
}

Here are the both dependencies relating to testing:

    [...]
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.8.2</version>
        <scope>test</scope>
    </dependency>
    [...]

Thank you for you help. It would be nice not to just stay at JUnit4.

Edit: I also have the same behavior with another testclass which only is annotated with @DataJpaTest and having @Autowire fields. Their null without the @RunWith

CodePudding user response:

If you take a better look at your test you will see that your @Test annotation is still from the old org.junit package. Which is JUnit4. Which means your test is actually being run with JUnit4 and not JUnit5. Which basically ignores all the annotations and thus also @WebMvcTest.

To fix make sure your @Test annotations are also from the proper org.junit.jupiter.api package to make it a proper JUnit5 test.

CodePudding user response:

There is a possibility that you are using @RequiredArgsConstructorin your controller.

Make sure that you have specified the service field as final. The @RequiredArgsConstructor documentation states that a constructor will be created with parameters for uninitialized final or @NonNull fields. If SkillTileService dependency was not marked as final it's not added as a constructor parameter.

So while the mock itself is not null, it still may be never injected into the tested controller.

  •  Tags:  
  • Related