Unit Test or Integration Test in Spring Boot

Tags: , , , ,



I looked various tutorial online related to testing in Spring Boot and got confused by the way the tests were referred.

Some articles refer to controller tests that use @WebMvcTest annotation as Unit Test whereas some refer it as Integration Test. Not sure which one is correct.

Same questions applies to Repository layer test with @DataJpaTest.

I have following two tests written in my application, one for the controller and another one for the repository.

At the bottom I have some questions regarding both. Please guide.

UserControllerTest.java

@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private UserRepository userRepository;

    @Test
    public void signUp() throws Exception {
        this.mockMvc.perform(get("/signup")).andExpect(status().isOk());
    }

}

UserRepositoryTest.java

@RunWith(SpringRunner.class)
@DataJpaTest
public class UserRepositoryTest {

    @Autowired
    private TestEntityManager entityManager;
    @Autowired
    private UserRepository userRepository;

    @Test
    public void whenFindByName_thenReturnEmployee() {
        // given
        User u = new User();
        u.setName("ab");
        u.setEmail("ab@cd.com");
        entityManager.persistAndFlush(u);
        // when
        Optional<User> user = userRepository.findById(1L);
        // then
        assertTrue(user.isPresent());
    }

}

My questions are:

  1. Does the annotation @WebMvcTest, @DataJpaTest or @SpringBootTest determines the type of test (Unit or Integration) or is it the use of @MockBean within the test that determines it?
  2. Assuming that UserControllerTest.java is a Unit test we are mocking the userRepository dependency here with @MockBean private UserRepository userRepository whereas in UserRepositoryTest.java we are autowiring it with @Autowired private UserRepository userRepository. Why ??

Answer

Why do you need spring to do unit testing? You can only use Mockito to do so without the need to start spring context. This is explained and discussed in details here: https://reflectoring.io/unit-testing-spring-boot/

It’s also very confusing for me when it comes to using @MockBean! Is that considered a unit or an integration test? In my opinion, even we’re using a mocked bean, but we’re still running within spring context and for me this is an integration test (as a unit test doesn’t need any spring context to run within). Same site that Brandon mentioned considers @MockBean an integration test https://www.baeldung.com/java-spring-mockito-mock-mockbean.

Image from above site

From Brandon response: “Integration tests should not contain any mocking and both types of testing should be run separately.”

What if you want to test an api starting from the controller all the way to DB, but you want to exclude other systems (like kafka or external Microservices)? How would you achieve this? You definitely need @MockBean. This is an integration test even it has mocked beans.

In summary (based on my experience and after searching and reading a lot of contradicting info for days). Here is my opinion:

  • I would say, stay away from using spring for unit testing as much as possible and just use Mockito or another framework that doesn’t need spring context. For example, when writing a test for a service class to test some calculation logic, we don’t need spring context and this is a PURE unit test.
  • We still can write PURE unit tests for controller classes. We can do that by calling the methods in the controller, then assert that these methods did what is expected (e.g. calling the right underlying methods with correct parameters ..etc). Basically the same way when writing a unit test for a service class. (Maybe these aren’t needed if it’s already gonna be covered in the following types of tests?)
  • We still can write pure unit tests for apis without any spring context. This described here. I tried and it worked for me. I’ll paste the code at the end of the post.
  • When running a test in spring context, this is considered an integration test even if you’re using @MockBean. An example of this: if we want to test an api starting from the controller all the way to the DB, but we want to exclude other systems (like kafka, email, or other external Microservices). How would we achieve this? We definitely need @MockBean. This is an integration test even though it uses some mocked beans.
  • I think the most confusing part is when testing the api layer only using spring as UserControllerTest in the question does (I mean by that calling the api and making sure it returns the right status code and response format). Is that considered a unit test or an integration test? It is not a unit as unit tests don’t need spring context to run within. It is actually something in between unit and integration tests. This source explains this concept very well https://blog.marcnuri.com/mockmvc-spring-mvc-framework/ (more specifically MockMvc standalone setup) So I think, it goes back then to the team where to place these tests (in the unit test folder, in the integration test folder, in a separate folder?) Also a good naming convention is needed to be used to avoid any confusion with pure unit tests or pure integration tests for the same class. From what I saw, most teams consider those unit tests, but I am not sure if that is the best practice!

    //unit test to call an api using MockMvc and mockito only
    @RunWith(MockitoJUnitRunner.class)
    public class UserControllerTest {
    
    private MockMvc mockMvc;
    @Mock
    UserService userService;
    @InjectMocks
    UserController controllerUnderTest;
    
    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(controllerUnderTest).build();
    }
    
    @Test
    public void testGetUser() throws Exception {
    
        //given:
        when(userService.getUser(.......)).thenReturn(....);
    
        //when:
        String url = "http://localhost:8081/api/ ....your url";
    
        //then:
        this.mockMvc.perform(get(url)).andDo(print()).andExpect(status().isOk());
    }
    

    }

Hope that helps and please let me know if there is any better opinion because I struggled a lot with that 🙂



Source: stackoverflow