SpringBoot - JUnit unit test
In this section, you learn about spring boot using Junit for unit testing.
1. JUnit overview
1.1 introduction to JUnit
JUnit 5 is the next generation of JUnit. The goal is to create an up-to-date foundation for developer-side testing on the JVM. This includes focusing on Java 8 and above, as well as enabling many different styles of testing.
JUnit 5 is the result of JUnit Lambda and its crowdfunding campaign on Indiegogo.
About - JUnit 5
JUnit is a unit testing framework for the Java language. It was established by Kent beck and Erich Gamma, and gradually became the most successful xUnit family of sUnit from Kent Beck. JUnit has its own JUnit extended ecosystem. Most java development environments have integrated JUnit as a unit testing tool.
JUnit is a regression testing framework written by Erich Gamma and Kent Beck. JUnit test is a programmer test, that is, the so-called white box test, because the programmer knows How and What functions the tested software completes. JUnit is a framework. If you inherit the TestCase class, you can use JUnit for automatic testing.
junit - Baidu Encyclopedia
1.2 JUnit5 features
As the latest version of JUnit framework, JUnit 5 is very different from the previous version of JUnit framework. It consists of several different modules of three different subprojects:
Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngine API for developing a testing framework that runs on the platform. Furthermore, the platform provides a Console Launcher to launch the platform from the command line and a JUnit 4 based Runner for running any TestEngine on the platform in a JUnit 4 based environment. First-class support for the JUnit Platform also exists in popular IDEs (see IntelliJ IDEA, Eclipse, NetBeans, and Visual Studio Code) and build tools (see Gradle, Maven, and Ant).
JUnit Jupiter is the combination of the new programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.
JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform. It requires JUnit 4.12 or later to be present on the class/module path.
What is JUnit 5? - JUnit 5 User Guide
JUnit Platform: JUnit Platform is the basis for starting the test framework on the JVM. It supports not only JUnit's self-made test engine, but also other test engines.
JUnit Jupiter: JUnit Jupiter provides a new programming model for JUnit 5 and is the core of JUnit 5's new features. A test engine is included internally to run on the JUnit Platform.
JUnit Vintage: since JUnit has developed for many years, in order to take care of old projects, JUnit Vintage provides a test engine compatible with JUnit 4.x and JUnit 3.x.
1.3 spring boot integration JUnit
Spring boot version 2.2.0 began to introduce JUnit 5 as the default library for unit testing. SpringBoot 2.4 removes the default dependency on JUnit Vintage. If you need to be compatible with JUnit 4, you need to introduce it yourself.
JUnit 5's Vintage Engine Removed from spring-boot-starter-test
If you upgrade to Spring Boot 2.4 and see test compilation errors for JUnit classes such as org.junit.Test, this may be because JUnit 5's vintage engine has been removed from spring-boot-starter-test. The vintage engine allows tests written with JUnit 4 to be run by JUnit 5. If you do not want to migrate your tests to JUnit 5 and wish to continue using JUnit 4, add a dependency on the Vintage Engine, as shown in the following example for Maven:
<dependency> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.hamcrest</groupId> <artifactId>hamcrest-core</artifactId> </exclusion> </exclusions> </dependency>
Spring Boot 2.4 Release Notes - GitHub/Spring-projects
2. Basic use of unit test
2.1 importing unit test module starter
Maven introduces SpringBoot unit test module starter:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <!-- use SpringBoot New version management mechanism --> </dependency>
2.2 write and analyze simple test classes
The existing business layer needs to test its CRUD function:
@Service public class UserServiceImpl implements UserService { private UserMapper mapper; @Autowired public void setMapper(UserMapper mapper) { this.mapper = mapper; } /** * Get user record according to ID * * @param id ID * @return User record */ public User getUserById(int id) { return mapper.selectUserById(id); } }
Write a simple test class according to the above code to test its CRUD function:
@SpringBootTest @Slf4j public class TestUserServiceImpl { private UserService service; @Autowired public void setService(UserService service) { this.service = service; } @Test public void testGetUserById() { log.info("Test parameters:{},Test results:{}", 1, service.getUserById(1)); } }
Run the test method and print the results on the console:
2021-10-12 10:32:25.709 INFO 6420 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting... 2021-10-12 10:32:25.927 INFO 6420 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed. 2021-10-12 10:32:25.979 INFO 6420 --- [ main] pers.dyj123.test.TestUserServiceImpl : Test parameters: 1, test results: Account(id=1, name=Zhang San, age=24)
Simple analysis test class:
- The test class needs @ SpringBootTest annotation to indicate that it is a SpringBoot test class;
- @The Test test method can be run directly without writing the main method;
- The test class of SpringBoot can use Spring function (using @ Autowired auto assembly function, etc.);
- Naming specification for test classes and test methods: test classes shall be named according to test + class names to be tested, and test methods shall be named according to test + method names to be tested.
3. JUnit5 common notes
JUnit 5 provides many common annotations. JUnit 5 annotations are different from JUnit 4 annotations:
annotation | function |
---|---|
@Test | The representation method is a Test method. However, unlike @ Test in JUnit4, it has a very single responsibility and cannot declare any attributes, Expanded testing will be provided by Jupiter with additional testing |
@ParameterizedTest | Indicates that the test method is parametric test, which will be described in detail in the second half |
@RepeatedTest | Indicates that the test method is repeatable |
@DisplayName | Set the presentation name for the test class or test method |
@BeforeEach | The method annotated with this annotation is executed before each test method is executed |
@AfterEach | The method annotated with this annotation is executed after each test method is executed |
@BeforeAll | The method annotated with this annotation will be executed before all test methods are executed |
@AfterAll | The method annotated with this annotation is executed after all test methods are executed |
@Tag | Represents the unit test category, similar to @ categories in JUnit 4 |
@Disabled | Indicates that the test class or test method is not executed, similar to @ Ignore in JUnit 4 |
@Timeout | Indicates that the test method will return an error if it runs beyond the specified time |
@ExtendWith | Provide extended class references for test classes or test methods |
For more information on the introduction and use of annotations, please refer to JUnit official documents .
3.1 @DisplayName
@The DisplayName annotation is used to set the name of the test class or test method. The parameters are as follows:
parameter | type | effect |
---|---|---|
value | String | Specify display name |
Usage:
@SpringBootTest @DisplayName("annotation@DisplayName Test class") public class TestDisplayName { @Test @DisplayName("annotation@DisplayName test method") public void testDisplayName() { System.out.println("Testing Examples "); } }
Run the test method and test results:
3.2 @BeforeEach and @ AfterEach
@The BeforeEach and @ AfterEach annotations are used to execute some methods before and after the execution of each test method. This annotation has no parameters.
Usage:
@SpringBootTest public class TestEach { @BeforeEach public void testBeforeEach() { System.out.println("Yes@BeforeEach method"); } @AfterEach public void testAfterEach() { System.out.println("Yes@AfterEach method"); } @Test public void test1() { System.out.println("Yes test1 method"); } @Test public void test2() { System.out.println("Yes test2 method"); } }
Perform all test methods and test results:
3.3 @BeforeAll and @ AfterAll
@The BeforeAll and @ AfterAll annotations are used to execute some methods before and after the execution of all test methods. This annotation has no parameters.
It should be noted that this annotation is recommended for static methods:
Such methods are inherited (unless they are hidden or overridden) and must be static (unless the "per-class" test instance lifecycle is used).
2.1. Annotations/@BeforeAll - JUnit 5 User Guide
Usage:
@SpringBootTest public class TestAll { @BeforeAll static void testBeforeAll() { System.out.println("Yes@BeforeAll method"); } @AfterAll static void testAfterAll() { System.out.println("Yes@AfterAll method"); } @Test public void test1() { System.out.println("Yes test1 method"); } @Test public void test2() { System.out.println("Yes test2 method"); } }
Perform all test methods and test results:
3.4 @Disabled
The test method marked with the @ Disabled annotation will be Disabled and the skipped test will not be executed; All test methods of the marked test class will not be executed. The parameters are as follows:
parameter | type | effect |
---|---|---|
value | String | Specify the reason for being disabled |
Usage:
@SpringBootTest public class TestDisabled { @Test @Disabled public void test1() { System.out.println("Yes test1 method"); } @Test public void test2() { System.out.println("Yes test2 method"); } }
Perform all test methods and test results:
3.5 @Timeout
If the execution of the test method marked by @ Timeout annotation exceeds the set time, it will report an error and end. The parameters are as follows:
parameter | type | effect |
---|---|---|
value | long | Specifies the number of time outs |
units | TimeUnit | Specifies the time-out unit |
Usage:
@SpringBootTest public class TestTimeout { @Test @Timeout(value = 500, unit = TimeUnit.MILLISECONDS) // Time limited 500 ms public void testTimeout() throws InterruptedException { Thread.sleep(600); // Thread sleep 600 ms System.out.println("Yes testTimeout method"); } }
Execute the test method and test results:
You can see that if the timeout is too long, the method will be forced to stop running.
3.6 @ExtendWith
@The ExtendWith annotation is used to add or use functional extensions of other test frameworks in the test. It has the same effect as the @ RunWith annotation in JUnit4. The parameters are as follows:
parameter | type | effect |
---|---|---|
value | Class<? extends Extension>[] | Specify the class to add |
In JUnit 4, if we want to use the Spring function, we need to mark @ RunWith(SpringJUnit4ClassRunner.class) in the test class,
In versions above SpringBoot 2.4, JUnit 5 uses the Spring function. We only need to mark the @ SpringBootTest annotation in the test class.
Analyze @ SpringBootTest annotation:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @BootstrapWith(SpringBootTestContextBootstrapper.class) @ExtendWith(SpringExtension.class) // Add Spring function extensions using @ ExtendWith public @interface SpringBootTest { // ... omit some code }
It is found that the @ SpringBootTest annotation has used @ ExtendWith to add Spring function extensions for us, so the test class only needs to annotate the @ SpringBootTest annotation to use the Spring function.
3.7 @RepeatedTest
@The RepeatedTest annotation can make the test method execute repeatedly, and the number of repeated executions can be set. The parameters are as follows:
parameter | type | effect |
---|---|---|
value | int | Number of cycles executed |
name | String | Set the display name when the loop is executed |
JUnit provides two display names:
- SHORT_ DISPLAY_ Name (default): "repetition {currentRepetition} of {totalRepetitions}"
- LONG_DISPLAY_NAME: "{displayName} :: repetition {currentRepetition} of {totalRepetitions}"
The above three placeholders: {DisplayName} is the display name set with the @ DisplayName annotation, {currentRepetition} is the current number of cycles, {totalRepetitions} is the total number of cycles.
You can use the above three placeholders to customize the presentation name of the loop execution.
Usage:
@SpringBootTest public class TestRepeatedTest { @RepeatedTest(value = 3, name = RepeatedTest.LONG_DISPLAY_NAME) @DisplayName("annotation@RepeatedTest test method") public void testRepeatedTest() { System.out.println("Yes testRepeatedTest method"); } }
Execute the test method and test results:
4. Assertion mechanism
Assertions are the core part of the test method. They are used to verify the conditions to be met by the test, check whether the data returned by the business logic is reasonable, and the characteristics of the assertion mechanism are as follows:
- JUnit 5's built-in assertion methods are static methods in org.junit.jupiter.api.Assertions.
- Each assertion method has a message parameter that allows you to specify custom error messages.
- Once the assertion judgment in the test method fails, the code after the assertion will not run, and the test method test fails.
The following will briefly introduce some assertion methods. For the usage and more information of other assertion methods, you can refer to JUnit 5 official document .
4.1 simple assertion
The following methods are used for simple validation of a single value:
method | effect |
---|---|
assertEquals | Judge whether two objects are equivalent (equivalent to equal) |
assertNotEquals | Judge whether two objects are not equivalent (equivalent to! equal) |
assertSame | Judge whether two object references point to the same object (equivalent to = =) |
assertNotSame | Judge whether two object references point to the same object (equivalent to! =) |
assertTrue | Determines whether the given Boolean value is true |
assertFalse | Determines whether the given Boolean value is false |
assertNull | Determines whether the given object reference is null |
assertNotNull | Determines whether the given object reference is not null |
Use the assertEquals(int expected, int actual) method to test the simple assertion mechanism:
@Test @DisplayName("Test simple assertions") public void testAssertions() { int sum = 4 + 5; // Analog business logic Assertions.assertEquals(10, sum); // Perform assertion check System.out.println("Yes testSimpleAssertions method"); }
Execute the test method and test results:
4.2 array assertion
JUnit provides array assertion methods (part):
[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-gfajde6k-16340401079319) (H: \ Documents Library \ idea projects \ readmemage \ springboot_test_junit_ assertion mechanism array assertion array assertion method display. JPG)]
Use the assertArraysEqual(int[] expected, int[] actual, String message) method to test the array assertion mechanism:
@Test @DisplayName("Test array assertion") public void testArrayAssertions() { Assertions.assertArrayEquals(new int[]{1, 2}, new int[]{2, 1}, "Array mismatch"); System.out.println("Yes testArrayAssertions method"); }
Execute the test method and test results:
Array assertion principle:
// org.junit.jupiter.api.AssertArrayEquals.assertArrayEquals(int[], int[], java.util.Deque<java.lang.Integer>, java.lang.Object) // AssertArrayEquals.java Line:233~247 private static void assertArrayEquals(int[] expected, int[] actual, Deque<Integer> indexes, Object messageOrSupplier) { if (expected == actual) { return; } assertArraysNotNull(expected, actual, indexes, messageOrSupplier); assertArraysHaveSameLength(expected.length, actual.length, indexes, messageOrSupplier); for (int i = 0; i < expected.length; i++) { // Use the for loop to use the elements of two arrays= One by one comparison. If an element does not match, the judgment fails if (expected[i] != actual[i]) { failArraysNotEqual(expected[i], actual[i], nullSafeIndexes(indexes, i), messageOrSupplier); } } }
4.3 combined assertions
JUnit provides the combined assertion method assertAll(String heading, Executable... executables), which can combine multiple assertions into one assertion. If a single assertion fails, they all fail. The parameters are as follows:
- heading: specify custom error information;
- executables: Executable is a functional interface that allows you to write code to be tested using lambda expressions.
Usage:
@Test @DisplayName("Test combination assertion") public void testAllAssertions() { Assertions.assertAll( "Combination assertion judgment failed", () -> Assertions.assertEquals(1.0, 2.0), () -> Assertions.assertEquals("Test combination assertion", "Test combination assertion") ); System.out.println("Yes testAllAssertions method"); }
Execute the test method and test results:
4.4 exception assertion
JUnit provides exception assertion methods assertThrows and assertdoesnottthrow to judge whether a specific exception is thrown and whether no exception is thrown respectively.
Usage:
@Test @DisplayName("Test exception assertion assertThrows") public void testAssertThrows() { Assertions.assertThrows( ArithmeticException.class, () -> { int i = 10 / 1; } ); System.out.println("Yes testAssertThrows method"); } @Test @DisplayName("Test exception assertion assertDoesNotThrow") public void testAssertDoesNotThrow() { Assertions.assertDoesNotThrow( () -> { throw new NullPointerException(); } ); System.out.println("Yes testAssertDoesNotThrow method"); }
Perform all test methods and test results:
4.5 timeout assertion
JUnit provides the timeout assertion method asserttimeout (duration timeout, executable). If the execution method times out, the assertion will fail.
Usage:
@Test @DisplayName("Test timeout assertion") public void testTimeoutAssertions() { Assertions.assertTimeout( Duration.ofMillis(500), () -> { Thread.sleep(600); } ); System.out.println("Yes testTimeoutAssertions method"); }
Execute the test method and test results:
4.6 failure assertion
JUnit provides the failure assertion method fail. The test method using this assertion method must assert failure. It is often used to detect that the method that cannot be called is illegally called or some conditions are judged incorrectly.
Usage:
@Test @DisplayName("Test failure assertion") public void testFailAssertions() { Assertions.fail("This method cannot be called"); System.out.println("Yes testFailAssertions method"); }
Execute the test method and test results:
5. Preconditions
Preconditions (assumptions) in JUnit 5 are similar to assertions. The difference is that unsatisfied assertions will fail the test method, while unsatisfied preconditions will only terminate the execution of the test method (equivalent to marked with @ Disabled annotation).
Preconditions can be regarded as the premise of test method execution. When the premise is not met, there is no need to continue execution.
The built-in precondition methods of JUnit 5 are all static methods in org.junit.jupiter.api.Assumptions:
Usage:
@SpringBootTest public class TestAssumptions { @Test @DisplayName("Test preconditions are true") public void testAssumeTrue() { Assumptions.assumeTrue(false, "Preconditions need to be true"); // Preconditions not met System.out.println("Yes testAssumeTrue method"); } @Test @DisplayName("Test preconditions are false") public void testAssumeFalse() { Assumptions.assumeFalse(true, "Preconditions need to be false"); // Preconditions not met System.out.println("Yes testAssumeFalse method"); } }
Perform all test methods and test results:
Test methods that do not meet the preconditions will be skipped directly (not failed) and will not be executed.
6. Nested test
JUnit supports adding test classes inside test classes, which are annotated with @ Nested annotation.
- When the test method of the external test class is executed, the methods annotated with @ BeforeEach, @ AfterEach and other annotations of the internal test class will not be executed;
- When the test method of the internal test class is executed, the methods annotated with @ BeforeEach, @ AfterEach and other annotations of the internal test class are executed normally.
For example, write a test method:
@SpringBootTest public class TestNested { List<Integer> list; // Declare a List collection @BeforeEach public void outerBeforeEach() { list = new ArrayList<>(); // The BeforeEach method of the external test class initializes the List collection } @Test @DisplayName("Test method 1 of external test class") public void outerTest1() { Assertions.assertNull(list); // The BeforeEach method of the external test class has initialized the List collection, so it is not null. The assertion failed } @Test @DisplayName("Test method of external test class 2") public void outerTest2() { Assertions.assertTrue(list.isEmpty()); // The external test class does not put elements into the List collection. When this method is executed, the BeforeEach method of the internal test class has not been executed, so the List collection is empty and the assertion is successful } @Nested class TestNestedInner { @BeforeEach public void innerBeforeEach() { list.add(1); // The BeforeEach method of the internal test class puts an element into the List collection } @Test @DisplayName("Test method of internal test class 1") public void innerTest1() { Assertions.assertNull(list); // The BeforeEach method of the external test class has initialized the List collection, so it is not null. The assertion failed } @Test @DisplayName("Test method of internal test class 2") public void innerTest2() { Assertions.assertTrue(list.isEmpty()); // The BeforeEach method of the internal test class has put an element into the List collection, so the List collection is not empty, and the assertion fails } } }
Perform all test methods and test results:
7. Parametric test
Parametric testing is a very important new feature of JUnit 5. It makes it possible to run tests multiple times with different parameters, and also brings a lot of convenience to our unit testing.
Using @ ValueSource and other annotations to specify input parameters, we can use different parameters for multiple unit tests without adding a unit test every time a parameter is added, saving a lot of redundant code:
annotation | effect |
---|---|
@ValueSource | Specify the input parameter source for parameterized tests, and support basic types, String types and Class types |
@NullSource | Provide a null input parameter for the parameterized test |
@EnumSource | Provide an enumeration type input parameter for parameterized tests |
@CsvFileSource | Read the content of the specified CSV file as the input parameter of the parametric test |
@MethodSource | Read the return value of the specified method as the input parameter of the parameterized test (note that the return value of the method needs to be a stream) |
The test method requiring parametric testing needs to be annotated with the @ ParameterizedTest annotation, and the above annotation needs to be used to provide the input source.
For other input sources and detailed descriptions, please refer to JUnit official documents .
Use @ ValueSource and @ MethodSource annotation examples to write test methods:
@SpringBootTest public class TestParameterizedTest { @ParameterizedTest @ValueSource(ints = {1, 2, 3}) // Pass in an array as an input parameter public void testValueSource(int param) { System.out.println("Yes testValueSource Methods, para" + param + "Second execution"); } @ParameterizedTest @MethodSource("paramStream") // Use the stream returned by paramStream as the input parameter public void testMethodSource(int param) { System.out.println("Yes testMethodSource Methods, para" + param + "Second execution"); } static Stream<Integer> paramStream() { return Stream.of(1, 2, 3); } }
Perform all test methods and test results: