[JAVA] WebAPI unit test and integration test with SpringBoot + Junit5, 4 patterns

Web API unit test and integration test

When creating a new Web API with PJ, I investigated what kind of test can be created with Junit, so make a note.

table of contents

Test code

  1. [Unit test (test by class)](# 1 Unit test Test by class)
  2. [Integration test 1 (Any multiple class integration)](# 2 Integration test 1 Any multiple class integration)
  3. [Integration test 2 (Integration of all classes, local environment)](# 3 Integration test 2 Integration of all classes, local environment)
  4. [Integration test 3 (Integration of all classes, arbitrary environment)](# 4 Integration test 3 Integration of all classes, arbitrary environment)

Code to be tested

1. Unit test (test by class)

Insert mock with mockito without depending on other classes. Testing is possible without waiting for the implementation of other classes.

EmployeeControllerSliceTest


import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.MockMvc;
import com.example.demo.domain.Employee;
import com.example.demo.usecase.GetEmployeeUsecase;
@WebMvcTest(EmployeeController.class)
class EmployeeControllerSliceTest {
    /**Mock to inject into test target*/
    @MockBean
    GetEmployeeUsecase mockedUsecase;
    /**Client for connection test*/
    @Autowired
    private MockMvc mvc;
    @Test
    void testGetOne() throws Exception {
        //Set mock movement
        when(this.mockedUsecase.getEmployee(anyString())).thenReturn(Optional.ofNullable(new Employee("foo", "bar")));
        //Run the test
        this.mvc.perform(get("/employees/{employeeId}", "123")) //
        .andExpect(status().is(200)) //
        .andExpect(content().json("{\"employeeId\":\"foo\",\"name\":\"bar\"}")); //Mock data regardless of query
    }
}

reference Auto-configured Spring MVC Tests Mocking and Spying Beans

2. Integration test 1 (integration of any multiple classes)

Can be tested by explicitly declaring dependence on other classes.

EmployeeControllerSliceAndImportTest


package com.example.demo.presentation;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.web.servlet.MockMvc;
import com.example.demo.infrastructure.TempEmployeeRepositoryImp;
import com.example.demo.usecase.GetEmployeeUsecase;
@WebMvcTest(EmployeeController.class)
@Import({ GetEmployeeUsecase.class, TempEmployeeRepositoryImp.class })
class EmployeeControllerSliceAndImportTest {
    /**Client for connection test*/
    @Autowired
    private MockMvc mvc;
    @Test
    void testGetOne() throws Exception {
        //Run
        this.mvc.perform(get("/employees/{employeeId}", "123")) //
        //Verification
        .andExpect(status().is(200)) //
        .andExpect(content().json("{\"employeeId\":\"123\",\"name\":\"Taro\"}")); //Data obtained from Repository
    }
}

reference Auto-configured Spring MVC Tests

3. Integration test 2 (integration of all classes, local environment)

Local testing is possible in an environment that is almost the same as when the server is actually started.

EmployeeControllerOnLocalhostTest


package com.example.demo.presentation;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class EmployeeControllerOnLocalhostTest {
    /**Client for connection test*/
    WebTestClient client = WebTestClient.bindToServer().build();
    /**Connection destination port number*/
    @LocalServerPort
    private int port;
    @Test
    void test() throws Exception {
        //Run
        this.client.get().uri("http://localhost:" + this.port + "/employees/{employeeId}", "123").exchange() //
        //Verification
        .expectStatus().isEqualTo(HttpStatus.valueOf(200)) //
        .expectBody(String.class).isEqualTo("{\"employeeId\":\"123\",\"name\":\"Taro\"}");
    }
}

reference Testing with a running server WebTestClient Spring WebClient Requests with Parameters

4. Integration test 3 (integration of all classes, arbitrary environment)

You can connect to any running server and test its behavior. It is necessary to start the server separately before executing the Junit test. Conversely, you can test any running Web API server. It doesn't have to be Java.

EmployeeControllerOnExternalServerTest


package com.example.demo.presentation;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
import org.springframework.test.web.reactive.server.WebTestClient;
class EmployeeControllerOnExternalServerTest {
    /**Client for connection test*/
    WebTestClient client = WebTestClient.bindToServer().build();
    /**Connection destination port number*/
    private final int port = 8080;
    @Test
    void test() throws Exception {
        //Run
        this.client.get().uri("http://localhost:" + this.port + "/employees/{employeeId}", "123").exchange() //
        //Verification
        .expectStatus().isEqualTo(HttpStatus.valueOf(200)) //
        .expectBody(String.class).isEqualTo("{\"employeeId\":\"123\",\"name\":\"Taro\"}");
    }
}

reference WebTestClient Spring WebClient Requests with Parameters

Code to be tested

EmployeeController.java

EmployeeController


package com.example.demo.presentation;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.domain.Employee;
import com.example.demo.usecase.GetEmployeeUsecase;
@RestController
public class EmployeeController {
    @Autowired
    GetEmployeeUsecase getUsecase;
    @GetMapping("/employees/{employeeId}")
    Optional<Employee> getOne(@PathVariable final String employeeId) {
        return this.getUsecase.getEmployee(employeeId);
    }
}

GetEmployeeUsecase.java

GetEmployeeUsecase


package com.example.demo.usecase;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.domain.Employee;
import com.example.demo.domain.EmployeeRepository;
@Service
public class GetEmployeeUsecase {
    @Autowired
    EmployeeRepository repository;
    public Optional<Employee> getEmployee(final String employeeId) {
        return this.repository.findOneById(employeeId);
    }
}

Employee.java

Employee


package com.example.demo.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public class Employee {
    private String employeeId = null;
    private String name = null;
    @SuppressWarnings("unused")
    private Employee() {
    }
}

EmployeeRepository.java

EmployeeRepository


package com.example.demo.domain;
import java.util.Optional;
import org.springframework.stereotype.Repository;
public interface EmployeeRepository {
    Optional<Employee> findOneById(String employeeId);
}

TempEmployeeRepositoryImp.java

TempEmployeeRepositoryImp


package com.example.demo.infrastructure;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Repository;
import com.example.demo.domain.Employee;
import com.example.demo.domain.EmployeeRepository;
@Repository
public class TempEmployeeRepositoryImp implements EmployeeRepository {
    private final List<Employee> db;
    public TempEmployeeRepositoryImp() {
        this.db = new ArrayList<>();
        //As initial data
        this.db.add(new Employee("123", "Taro"));
    }
    @Override
    public Optional<Employee> findOneById(final String employeeId) {
        return this.db.stream().filter(e -> e.getEmployeeId().equals(employeeId)).findFirst();
    }
}

GitHub https://github.com/tmtmra/restControllerTestSample

Finally

I'm a beginner who started programming a year ago, so please give me various opinions and advice.

Recommended Posts

WebAPI unit test and integration test with SpringBoot + Junit5, 4 patterns
Unit test with Junit.
Mixin test cases with JUnit 5 and default methods
Integration Test with Gradle
Test Web API with junit
UnitTest with SpringBoot + JUnit + Mockito
[Java] Test private methods with JUnit
Copy and paste test with RSpec
Test Spring framework controller with Junit
Let's unit test with [rails] Rspec!
Introduce RSpec and write unit test code
Control test order in Junit4 with enumeration
How to test private scope with JUnit
JUnit 5 gradle test fails with lombok annotation
Java automated test implementation with JUnit 5 + Gradle
[Java] How to test for null with JUnit
About designing Spring Boot and unit test environment
[CircleCI 2.0] [Java] [Maven] [JUnit] Aggregate JUnit test results with CircleCI 2.0
Java automated test implementation with JUnit 5 + Apache Maven
How to test interrupts during Thread.sleep with JUnit
Build and test Java + Gradle applications with Wercker
Easy JUnit test of Elasticsearch 2018 version with embedded-elasticsearch
Overwrite the contents of config with Spring-boot + JUnit5
Test code using mock with JUnit (EasyMock center)
Unit test with Junit.
WebAPI unit test and integration test with SpringBoot + Junit5, 4 patterns
Introduce RSpec and write unit test code
Control test order in Junit4 with enumeration
About designing Spring Boot and unit test environment
Mixin test cases with JUnit 5 and default methods
Try JUnit test launch
Java Unit Test Library-Artery-Sample
Use Spring Test + Mockito + JUnit 4 for Spring Boot + Spring Retry unit tests