Unit Test or Integration Test in Spring Boot
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 :)
Unit tests run in isolation while integration tests bootstrap the Spring web context before execution starts.
UNIT TESTS
Running in isolation will sometimes require that you mock your dependencies based on the class you are testing. By doing this, you're allowing yourself to test very specific test cases end-to-end without having to worry about the overhead of service or domain layers. Hence, using Mockito or more specifically, the Mockito.mock() method which mocks object classes and DOES NOT replace any objects on the web context such as @MockBean.
INTEGRATION TESTS
Whereas, integration testing focuses on integrating different layers of the application such as the database. In regards to databases, most people utilize an in memory database such as H2 to test their domain layers/repositories. Integration tests SHOULD not contain any mocking and both types of testing should be run separately. This isn't to say that integration tests can not contain any mocking, but it isn't common since you already have isolated unit tests that test the various layers of your application which contain mocked dependencies!
E2E TESTS
If you are testing your application from end-to-end, you're better off not mocking anything other than your data, with proper cleanup. Testing frameworks such as Cucumber are great for end-to-end testing. Why would you mock different layers, you already have unit tests for that type of testing!
Resources: https://www.baeldung.com/spring-boot-testing and https://www.baeldung.com/java-spring-mockito-mock-mockbean