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:
- Does the annotation
@WebMvcTest, @DataJpaTest
or@SpringBootTest
determines the type of test (Unit
orIntegration
) or is it the use of@MockBean
within the test that determines it? - Assuming that
UserControllerTest.java
is a Unit test we are mocking theuserRepository
dependency here with@MockBean private UserRepository userRepository
whereas inUserRepositoryTest.java
we are autowiring it with@Autowired private UserRepository userRepository
. Why ??
Advertisement
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.
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 🙂