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("[email protected]");
entityManager.persistAndFlush(u);
// when
Optional<User> user = userRepository.findById(1L);
// then
assertTrue(user.isPresent());
}
}
My questions are:
@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?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 ??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 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 :)