Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Test or Integration Test in Spring Boot

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:

  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 ??
like image 259
Nital Avatar asked Feb 12 '19 20:02

Nital


People also ask

What is integration testing in Spring Boot?

Integration Testing With @SpringBootTest As the name suggests, integration tests focus on integrating different layers of the application. That also means no mocking is involved. Ideally, we should keep the integration tests separated from the unit tests and should not run along with the unit tests.

What is testentitymanager in Spring Boot?

The Spring Boot TestEntityManager is an alternative to the standard JPA EntityManager that provides methods commonly used when writing tests. EmployeeRepository is the component that we are going to test. Now let's write our first test case:

What is the difference between unit testing and integration testing?

In integration tests on the other side, we test the integration between components. Thanks to unit testing, we know that components behave as required individually, but we don't know how they'll work altogether.

What is Spring Boot starter test In JUnit?

The spring-boot-starter-test is the primary dependency that contains the majority of elements required for our tests. The H2 DB is our in-memory database. It eliminates the need for configuring and starting an actual database for test purposes. 3.1. JUnit 4


2 Answers

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 :)

like image 174
Mosheer Avatar answered Sep 24 '22 06:09

Mosheer


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

like image 41
Brandon Avatar answered Sep 23 '22 06:09

Brandon