[JAVA] Perform transaction confirmation test with Spring Boot

Introduction

In the team I belong to, if there is a problem, I write a test code, reproduce it (check the red bar), and then fix it. The other day, there was a problem that transaction control was missing, so I tried to write a test code, but since I was confused, I will organize it and post it. The test code is listed on Github (https://github.com/shimi58/transactiontest), so please refer to it.

Development environment

The environment built on Eclipse is as follows. I have created a Gradle project and passed it through the path.

environment version
Java 1.8
SpringBoot 2.2.6
MyBatis 2.1.0
H2 PostgresqlMode
Junit 5(Jupiter)

Implementation code (service class)

Register employee information (name, phone number, email address) and update the number in the employee table to the status table. It is a sample source such as.


/**
 * Employee service
 */
@Service
public class EmployeeService {

    @Autowired
    EmployeeRepository employeeRepository;

    /**
 * Employee registration
     */
 @Transactional // ← This was leaking
    public EmployeeNumber register(Employee employee) {

        employeeRepository.registerEmployee(employee);
        Employees employees = employeeRepository.findEmployees();

        EmployeeNumber employeeNumber = employees.number();
        employeeRepository.registerNumber(employeeNumber);

        return employeeNumber;

    }

}

In this sample source, it is updated as employee table → status table, but if the transaction is not effective and the update of the status table fails, it is retained in the number and status table held in the employee table. The number of cases is inconsistent.

By the way, the Controller class will be mentioned later when explaining the test code.

/**
 * Employee Controller
 */
@RestController
@RequestMapping("/employees")
public class EmployeesController {

    @Autowired
    EmployeeService employeeService;

    /**
 * Employee registration
     */
    @RequestMapping(value = "/register", method = {RequestMethod.POST})
    public String regist(@RequestBody Employee employee) {

        EmployeeNumber employeeNumber = new EmployeeNumber(0);

        try {
            employeeNumber = employeeService.register(employee);
        } catch (Exception e) {

 System.out.println ("processing error");
        }
        return employeeNumber.toString();


    }
}

How to test

Use ** @SpyBean **.

SpyBean is a Spring Boot function, and it is a mock object that only partially processes the class you want to test. In the case of this sample source, Mock the EmployeeRepository with SpyBean and

--registerEmployee: Employee registration process → Normal implementation --findEmployees: Employee table count processing → Normal execution --registerNumber: Status table update process → Error occurred

I want to create a situation like that.

Test code

The test is executed by calling the Controller class as SpringBootTest. The Controller class is calling the above service class.

@SpringBootTest
public class EmployeesControllerTransactionTest {

    MockMvc mockMvc;

    @Autowired
    private ObjectMapper mapper;

    @Autowired
    private EmployeesController employeesController;

 // Mock target
    @SpyBean
    private EmployeeRepository employeeRepository;


    /**
 * Transaction test
     *
 * When an error occurs when updating the number of employees after registering employee information in the Emploee table, <br>
 * Make sure you are rolling back employee information registration
     *
     * @throws Exception
     */
    @ParameterizedTest
 @CsvSource ({"Takao Hibi, 123-4345-2352, [email protected], 3"})
    public void testTransaction(String name, String phone, String mail, int expected)
            throws Exception {

        Employee employee = new Employee(name, phone, mail);


 // Convert request to Json format
        String json = mapper.writeValueAsString(employee);

 // Error occurrence setting
        doThrow(new RuntimeException()).when(employeeRepository)
                .registerNumber(Mockito.any(EmployeeNumber.class));

        this.mockMvc = MockMvcBuilders.standaloneSetup(employeesController).build();

 // Issue request
        MvcResult result = mockMvc.perform(
                post("/employees/register").contentType(MediaType.APPLICATION_JSON).content(json))
                .andExpect(status().isOk()).andReturn();

        String response = result.getResponse().getContentAsString();
        Employees employees = employeeRepository.findEmployees();

        System.out.println(response);
        System.out.println(employees.toString());

        EmployeeNumber actual = employees.number();

 // Because it will be rolled back, check that it is in the state of 3 before registration
        assertEquals(expected, actual.getValue().intValue());
    }

Points of test implementation

@SpringBootTest
public class EmployeesControllerTransactionTest {

It is indispensable because it will not DI unless SpringBootTest is attached.

    @SpyBean
    private EmployeeRepository employeeRepository;

Declare the repository to be Mocked.

    @ParameterizedTest
 @CsvSource ({"Takao Hibi, 123-4345-2352, [email protected], 3"})

Although it is not related to this article, by using CsvSource, you can perform variation test with one method. I had a long history of Junit4, so I was impressed when I realized that I could do this. By the way, the name was borrowed from Amazing Name Generator. It has become a convenient world. (More irrelevant.)

 // Error occurrence setting
        doThrow(new RuntimeException()).when(employeeRepository)
                .registerNumber(Mockito.any(EmployeeNumber.class));

Here, it is declared that a RuntimeException will be generated when processing registerNumber.

this.mockMvc = MockMvcBuilders.standaloneSetup(employeesController).build();

Dohamari point ①. If you don't set it up standalone, mockMvc will drop with a nullpo.

        MvcResult result = mockMvc.perform(
                post("/employees/register").contentType(MediaType.APPLICATION_JSON).content(json))
                .andExpect(status().isOk()).andReturn();

Dohamari point ②. If it is not status (). isOk (), it will fly away (debug will not be returned). I was worried for an hour when the URL of the post was wrong and 404 was returned, and why the debug response did not occur.

Test execution result

When I ran the test code, it turned out to be a nice green bar! image.png

By the way, if you remove @Transactional in the service class, image.png

Yeah, it's a red bar. If the number of items in the employee table is different by one, an assertion error has occurred and the transaction can be confirmed.

Finally

I will look back alone.

What i found

--H2's in-memory DB is very convenient This is the first time I've touched it, and it's perfect for checking the operation of these sample codes. It would be even more perfect if you could connect with A5SQL. --Gradle is very convenient Just write the definition and it will pass through the path, so it will make a lot of progress. As an aside, I'm going to drop a good number of libraries by tracing the dependency, so checking the license makes me cry (laugh) --SpyBean is very convenient I think there are some things that can't be tested with Mock. ――It is difficult to write the process from 1 I should have written it in business such as how to receive Gradle and post, test code, but it took a long time to write from scratch. .. (But implementation is fun) --H2 Postgres Mode cannot Upsert It looks like H2 Database Postgres Mode Upsert. Therefore, I made sure to register the record in the status table with the initial build data so that only update is required. (Not cool ...)

What to do next

--Read and organize test-driven design to master the test path --Make various variations of test code work as a sample source so that other Mock processing works.

Recommended Posts

Perform transaction confirmation test with Spring Boot
Form class validation test with Spring Boot
Download with Spring Boot
Test controller with Mock MVC in Spring Boot
Generate barcode with Spring Boot
Hello World with Spring Boot
Implement GraphQL with Spring Boot
Get started with Spring boot
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
Hello World with Spring Boot!
Run LIFF with Spring Boot
SNS login with Spring Boot
File upload with Spring Boot
Spring Boot starting with copy
[JUnit 5] Write a validation test with Spring Boot! [Parameterization test]
How to perform UT with Excel as test data with Spring Boot + JUnit5 + DBUnit
Spring Boot starting with Docker
Hello World with Spring Boot
Set cookies with Spring Boot
Use Spring JDBC with Spring Boot
Add module with Spring Boot
Getting Started with Spring Boot
Create microservices with Spring Boot
I wrote a test with Spring Boot + JUnit 5 now
Send email with spring boot
Use Basic Authentication with Spring Boot
gRPC on Spring Boot with grpc-spring-boot-starter
Create an app with Spring Boot 2
Hot deploy with Spring Boot development
Database linkage with doma2 (Spring boot)
Write test code in Spring Boot
Spring Boot programming with VS Code
Until "Hello World" with Spring Boot
Inquiry application creation with Spring Boot
Get validation results with Spring Boot
(Intellij) Hello World with Spring Boot
Create an app with Spring Boot
Google Cloud Platform with Spring Boot 2.0.0
Use DBUnit for Spring Boot test
Check date correlation with Spring Boot
I tried GraphQL with Spring Boot
[Java] LINE integration with Spring Boot
Beginning with Spring Boot 0. Use Spring CLI
I tried Flyway with Spring Boot
Test Spring framework controller with Junit
Message cooperation started with Spring Boot
Spring Boot gradle build with Docker
Sample code to unit test a Spring Boot controller with MockMvc
Hello World with Eclipse + Spring Boot + Maven
Send regular notifications with LineNotify + Spring Boot
HTTPS with Spring Boot and Let's Encrypt
Try using Spring Boot with VS Code
Spring Boot @WebMvcTest test enables Spring Security default security
Start web application development with Spring Boot
Launch Nginx + Spring Boot application with docker-compose
I tried Lazy Initialization with Spring Boot 2.2.0
Implement CRUD with Spring Boot + Thymeleaf + MySQL
Asynchronous processing with Spring Boot using @Async
Implement paging function with Spring Boot + Thymeleaf
(IntelliJ + gradle) Hello World with Spring Boot
Use cache with EhCashe 2.x with Spring Boot