[JUnit 5] Write a validation test with Spring Boot! [Parameterization test]

Introduction

Hello! This is the 5th series to play with Java from scratch. This time, we will make the validation test class that we made last time.

When spoiled, it is the type of Qiita post that has a failure example first. If you want to see an example of the parameterization test quickly, please skip to ◎ Good example

Click here for articles up to the last time ↓

  1. Building STS environment on mac
  2. Hello World with Spring Boot
  3. Create echo application with Spring Boot
  4. Create form validation with Spring Boot

Synopsis up to the last time

The current validation requirements are as follows. Annotation is added to the form class to check the following character types and the number of characters.

--Available character types: Half-width alphanumeric characters only --Character limit: 4 characters or more

画面収録 2020-07-21 23.39.15.mov.gif

Usage environment and version

[△ Bad example ①] Redundant by copying except for the setting part of the variable to be tested

Since we are validating with the form class, we will create a test class for the form class. There are four cases I would like to test this time.

  1. Normal system (no error)
  2. Abnormal system (incorrect number of characters)
  3. Abnormal system (character type invalid)
  4. Abnormal system (illegal number of characters & incorrect character type)

However, even normal systems allow half-width alphanumeric characters, so if you want to perform a more rigorous test, you want a data pattern of only numbers, only alphabets, and alphanumerics. I also want to do data patterns such as hiragana, katakana, kanji, double-byte alphanumeric characters, symbols, etc. for abnormal characters with incorrect character types ... [^ 1]

The following code was created in a hurry with 3 normal patterns, 2 incorrect character count patterns, and 2 incorrect character type patterns. The JUnit test still passes, but the test execution and result verification are very redundant because the same code is repeated (boilerplate code) ... Moreover, abnormal kanji and hiragana, mixed character type patterns, and abnormal types in which both the number of characters and the character type are invalid have not been implemented yet. If you try to complete it as it is, the amount of code will increase further.

[^ 1]: Strictly speaking, the implementation of this form is almost only using the validation function of javax (I have not created my own validation etc.), so there is no need to thoroughly test it on the library user side. If something goes wrong with this, there is a fatal bug in the javax library itself. Please think that it is just an undercard for wanting to introduce the parameterization test (laugh)

EchoFormTest.java



package com.example.form;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertNull;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Validator;

@SpringBootTest
public class EchoFormTest {
	
	@Autowired
    Validator validator;

	private EchoForm testEchoForm = new EchoForm();
	private BindingResult bindingResult = new BindException(testEchoForm, "echoForm");
	
	/**
     *Normal system
     */
    @Test
    public void test_echo_Normal system_4 or more letters() {
    	//Test preparation
    	testEchoForm.setName("aaaa");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertNull(bindingResult.getFieldError());
    }

    @Test
    public void test_echo_Normal system_4 or more numbers() {
    	//Test preparation
    	testEchoForm.setName("1111");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertNull(bindingResult.getFieldError());
    }

    @Test
    public void test_echo_Normal system_4 or more alphanumeric characters() {
    	//Test preparation
    	testEchoForm.setName("aa11");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertNull(bindingResult.getFieldError());
    }
    
    /**
     *Abnormal system_insufficient number of characters
     */
    @Test
    public void test_echo_Abnormal system_Half-width English 3 characters() {
    	//Test preparation
    	testEchoForm.setName("aaa");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertThat(bindingResult.getFieldError().toString()).contains("Must be at least 4 characters.");
    }
    
    @Test
    public void test_echo_Abnormal system_3 single-byte characters() {
    	//Test preparation
    	testEchoForm.setName("111");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertThat(bindingResult.getFieldError().toString()).contains("Must be at least 4 characters.");
    }
    
    /**
     *Abnormal system_character type invalid
     */
    @Test
    public void test_echo_Abnormal system_Hiragana() {
    	//Test preparation
    	testEchoForm.setName("Ah ah");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertThat(bindingResult.getFieldError().toString()).contains("Only half-width alphanumeric characters are valid.");
    }
    
    @Test
    public void test_echo_Abnormal system_Katakana() {
    	//Test preparation
    	testEchoForm.setName("Aaaaaaa");
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertThat(bindingResult.getFieldError().toString()).contains("Only half-width alphanumeric characters are valid.");
    }
}

[× Bad example (2)] Loop the data pattern for statement

"I want to change only the test data with the same code for test execution and result verification ... That's it! Put the test data in an array, and use the for statement to execute and verify the results for each data! "

When you read the Java introductory book, redundant descriptions tend to be solved by for statements, so it's no wonder you think so. It can't be helped. Please say no (← experienced person).

Code that looks fine at first glance ... For statement that can be written shorter than [△ bad example ①] ... And do you know what's wrong with this code that goes through everything even if you actually run it in JUnit? ↓

EchoFormTest.java


   /**
     *Normal system
     */
    @Test
    public void test_echo_Normal system() {
    	String[] data = {"aaaa", "aa11", "1111"};
    	for(String testCase: data) {
    		//Test preparation
    		testEchoForm.setName(testCase);	
    		//Test implementation
    		validator.validate(testEchoForm, bindingResult);
    		//Result verification
    		assertNull(bindingResult.getFieldError());
    	}
    }

Actually, in this example, it is good if all the test data ends normally, but if the test fails with the data in the middle, the test will fail without the subsequent test data being executed. Not all data can be tested.

For example, if you try to make an error occur in the second and third of the three data as shown below ...

EchoFormTest.java


    /**
     *Normal system
     */
    @Test
    public void test_echo_Normal system() {
    	String[] dataPattern = {"aaaa", "err", "bad"}; //Err does not pass this test as 3 characters or less will result in an error
    	for(String testData: dataPattern) {
    		//Test preparation
    		testEchoForm.setName(testData);	
    		//Test implementation
    		validator.validate(testEchoForm, bindingResult);
    		//Result verification
    		assertNull(bindingResult.getFieldError());
    	}
    }

Since the test fails with the second data, the log only shows the second error. Since the third and subsequent data are not tested in the first place, it is not possible to determine whether the subsequent data is normal or abnormal.

スクリーンショット 2020-09-22 15.33.30.png

org.opentest4j.AssertionFailedError: expected: <null> but was: <Field error in object 'echoForm' on field 'name': rejected value [err]; codes [Size.echoForm.name,Size.name,Size.java.lang.String,Size]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [echoForm.name,name]; arguments []; default message [name],2147483647,4]; default message [Must be at least 4 characters.]>
	at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:55)
	at org.junit.jupiter.api.AssertNull.failNotNull(AssertNull.java:48)
	at org.junit.jupiter.api.AssertNull.assertNull(AssertNull.java:37)
	at org.junit.jupiter.api.AssertNull.assertNull(AssertNull.java:32)
	at org.junit.jupiter.api.Assertions.assertNull(Assertions.java:258)
	at com.example.form.EchoFormTest.test_echo_Normal system(EchoFormTest.java:97)

It would be scary to think that there would be 100 or 200 data patterns after that. No matter how redundant it is, it can be said that [△ bad example ①] is still better than this [× bad example ②] because it can fulfill its responsibilities as a test code.

[◎ Good example] Implemented by parameterization test

Sorry I made you wait! Here's a good example. Implementation in parameterized test.

EchoFormTest.java


    /**
     *Normal system
     */
    @ParameterizedTest
    @ValueSource(strings = {"aaaa", "aa11", "1111"})
    public void test_echo_Normal system(String s) {
        //Test preparation
    	testEchoForm.setName(s);	
    	//Test implementation
    	validator.validate(testEchoForm, bindingResult);
    	//Result verification
    	assertNull(bindingResult.getFieldError());
    }

There is no redundant code like [△ bad example ①]. Furthermore, what is good about parameterization is that it solves the problems of "subsequent tests are not executed" and "I do not know which test data was executed" that I saw in [× Bad example (2)]. Looking at the execution results below, you can see that all three test data have been completed normally.

スクリーンショット 2020-09-22 15.56.07.png

Also, increase the data and check if the first and fourth data are normal and the second and third data are incorrect.

EchoFormTest.java


    /**
     *Normal system
     */
    @ParameterizedTest
    @ValueSource(strings = {"aaaa", "err", "bad", "1111"}) //err and bad are out of characters
    public void test_echo_Normal system(String s) {
        //Test preparation
    	testEchoForm.setName(s);	
    	//Test implementation
    	validator.validate(testEchoForm, bindingResult);
    	//Result verification
    	assertNull(bindingResult.getFieldError());
    }

スクリーンショット 2020-09-22 16.03.07.png

Even after the second error occurred, the third and fourth data were properly verified, and the stack trace came out for each error! JUnit5 is too convenient ...

I will also post an example of an abnormal system.

EchoFormTest.java


    /**
     *Abnormal system_insufficient number of characters
     */
    @ParameterizedTest
    @ValueSource(strings = {"aaa", "111", "aa1"})
    public void test_echo_Abnormal system_Insufficient number of characters(String s) {
    	//Test preparation
    	testEchoForm.setName(s);
    	//Test implementation
        validator.validate(testEchoForm, bindingResult);
        //Result verification
        assertThat(bindingResult.getFieldError().toString()).contains("Must be at least 4 characters.");

I was able to verify this without any problems!

スクリーンショット 2020-09-22 16.18.04.png

Please also refer to the following Qiita (I used it as a reference this time!). JUnit 5 parameterization test is super convenient

It's a slapstick, but in the case of JUnit 4, the following site will be helpful. https://javaworld.helpfulness.jp/post-81/ I implemented it using @RunWith (Parameterized.class). Although the amount of code is larger than that of JUnit5, the parameterization test can be fully implemented in JUnit4. You can also use it with another test runner by creating it as an inner class of @ RunWith (Enclosed.class).

in conclusion

This time we looked at the parameterization test in JUnit 5. A bad example is how I actually wrote it when I didn't know the existence of the parameterized test itself ... It's actually easy to come up with a test with a for statement, so if you have done it, please consider changing to a parameterized test!

Thank you for reading!

Recommended Posts

[JUnit 5] Write a validation test with Spring Boot! [Parameterization test]
[JUnit 5 compatible] Write a test using JUnit 5 with Spring boot 2.2, 2.3
I wrote a test with Spring Boot + JUnit 5 now
Form class validation test with Spring Boot
How to write a unit test for Spring Boot 2
Write test code in Spring Boot
Get validation results with Spring Boot
Test Spring framework controller with Junit
Perform transaction confirmation test with Spring Boot
Sample code to unit test a Spring Boot controller with MockMvc
Let's write a test code for login function in Spring Boot
Create a website with Spring Boot + Gradle (jdk1.8.x)
Test controller with Mock MVC in Spring Boot
Create a simple search app with Spring Boot
Write RestController tests quickly with Spring Boot + Spock
[Java] Hello World with Java 14 x Spring Boot 2.3 x JUnit 5 ~
[Java] Article to add validation with Spring Boot 2.3.1.
Create a web api server with spring boot
Create a Spring Boot development environment with docker
Self-made Validation with Spring
Unit test with Junit.
Download with Spring Boot
How to perform UT with Excel as test data with Spring Boot + JUnit5 + DBUnit
Implement a simple Rest API with Spring Security with Spring Boot 2.0
A memorandum when creating a REST service with Spring Boot
Create a simple demo site with Spring Security with Spring Boot 2.1
Generate barcode with Spring Boot
Hello World with Spring Boot
Test Web API with junit
Get started with Spring boot
Hello World with Spring Boot!
Run LIFF with Spring Boot
SNS login with Spring Boot
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
Spring single item validation test
Send email with spring boot
Spring Boot validation message changes
A story packed with the basics of Spring Boot (solved)
Let's make a simple API with EC2 + RDS + Spring boot ①
Implement a simple Rest API with Spring Security & JWT with Spring Boot 2.0
Use Spring Test + Mockito + JUnit 4 for Spring Boot + Spring Retry unit tests
Implement a simple Web REST API server with Spring Boot + MySQL
Use Basic Authentication with Spring Boot
Let's make a book management web application with Spring Boot part1
Write a Reactive server with Micronaut
gRPC on Spring Boot with grpc-spring-boot-starter
Hot deploy with Spring Boot development
Let's make a book management web application with Spring Boot part3
Spring Boot programming with VS Code
Until "Hello World" with Spring Boot
Inquiry application creation with Spring Boot
Let's make a book management web application with Spring Boot part2
I made a simple search form with Spring Boot + GitHub Search API.
(Intellij) Hello World with Spring Boot
Create an app with Spring Boot