Article directory:
2. JUnit 5 common notes and tests
2.1 @DisplayName,@Disabled,@BeforeEach,@AfterEach,@BeforeAll,@AfterAll
1. Changes in JUnit 5
Spring boot version 2.2.0 began to introduce JUnit 5 as the default library for unit testing
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.
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage
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 of 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 JUint 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.
Note: springboot versions above 2.4 remove the default dependency on Vintage. If you need to be compatible with junit4, you need to introduce it yourself (you can't use the function @ Test of junit4)
JUnit 5's vintage engine removed from spring boot starter test. If you need to continue to be compatible with junit4, you need to introduce vintage by yourself
<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>
If you only use JUnit 5 features, you need to add the following dependencies.
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class SpringbootActuatorApplicationTests { @Test void contextLoads() { } }
2. JUnit 5 common notes and tests
JUnit 5 annotations are different from JUnit 4 annotations
- @Test: indicates that the method is a test method. However, unlike JUnit4's @ test, it has a very single responsibility and cannot declare any attributes. Jupiter will provide additional tests for expanded tests
- @ParameterizedTest: indicates that the method is parameterized test, which will be described in detail below
- @RepeatedTest: indicates that the method can be executed repeatedly, which will be described in detail below
- @DisplayName: set the display name for the test class or test method
- @Before each: indicates to execute before each unit test
- @After each: means to execute after each unit test
- @Before all: indicates to execute before all unit tests
- @After all: means to execute after all unit tests
- @Tag: indicates the unit test category, similar to @ Categories in JUnit4
- @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 exceeds the specified time
- @ExtendWith: provides an extension class reference for a test class or test method
2.1 @DisplayName,@Disabled,@BeforeEach,@AfterEach,@BeforeAll,@AfterAll
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { //----------------------------Common notes---------------------------- @DisplayName("test DisplayName annotation---Method 1") @Test public void testDisplayName1() { System.out.println(123); } @Disabled @DisplayName("test DisplayName annotation---Method 2") @Test public void testDisplayName2() { System.out.println(456); } @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } }
Let's open the @ Disabled annotation on the testDisplayName2 method. As you can see, this test method has been Disabled.
2.2 @Timeout
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } @DisplayName("test Timeout annotation") @Timeout(value = 5, unit = TimeUnit.MILLISECONDS) @Test public void testTimeout() throws InterruptedException { Thread.sleep(1000); } }
2.3 @RepeatedTest
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } @RepeatedTest(5) @Test public void testRepeatedTest() { System.out.println(1); } }
3. Assertion
Assertions are the core part of the test method, which is used to verify the conditions that the test needs to meet. These assertion methods are static methods of org.junit.jupiter.api.Assertions. The built-in assertions in JUnit 5 can be divided into the following categories:
Check whether the data returned by the business logic is reasonable.
After all test runs are completed, there will be a detailed test report;
3.1 simple assertion
Used for simple validation of a single value. For example:
method | explain |
assertEquals | Determines whether two objects or two primitive types are equal |
assertNotEquals | Determines whether two objects or two primitive types are not equal |
assertSame | Judge whether two object references point to the same object |
assertNotSame | Judge whether two object references point to different objects |
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 |
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } //----------------------------Assert---------------------------- /** * If the previous assertion fails, the subsequent code will no longer execute */ @DisplayName("Test simple assertions") @Test public void testSimpleAssertions() { int result = cal(1,2); assertEquals(5,result,"Business logic calculation result error...."); Object obj1 = new Object(); Object obj2 = new Object(); assertSame(obj1,obj2); } public int cal(int x, int y) { return x + y; } }
The assertEquals method judges that the operation results are different, that is, if the first assertion fails, the subsequent assertSame will not be executed.
When we modify the assertEquals method to make the assertion successful, the subsequent assertSame will continue to execute.
What you see at this time is the exception information printed by the assertSame method (the two objects are not equal).
assertEquals(3,result,"Business logic calculation result error....");
3.2 array assertion
Use the assertArrayEquals method to determine whether two objects or arrays of original types are equal.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } //----------------------------Assert---------------------------- @DisplayName("Test array assertion 1") @Test public void testArrayAssertions1() { assertArrayEquals(new int[]{1,2}, new int[]{1,2}); } @DisplayName("Test array assertion 2") @Test public void testArrayAssertions2() { assertArrayEquals(new int[]{3,4}, new int[]{1,2}, "Array contents are not equal...."); } }
3.3 combined assertions
The assertAll method accepts multiple instances of the org.junit.jupiter.api.Executable functional interface as assertions to be verified. These assertions can be easily provided through lambda expressions.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } //----------------------------Assert---------------------------- @DisplayName(("Test combination assertion")) @Test public void testAllAssertions() { assertAll("testAll", () -> assertTrue(true && true), () -> assertEquals(1,2)); //When the above two assertions are all successful, the following content will be printed System.out.println("success!!!"); } }
In composite assertions, when all assertions are successful, the code will execute down smoothly. Although assertTrue was executed successfully, assertEquals failed. All the following South will not be executed.
3.4 exception assertion
In JUnit 4, when you want to test the exception of a method, the ExpectedException variable annotated with @ Rule is still troublesome. JUnit 5 provides a new assertion method, Assertions.assertThrows(), which can be used in conjunction with functional programming.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } //----------------------------Assert---------------------------- @DisplayName("Test exception assertion") @Test public void testExceptionAssertions() { assertThrows(ArithmeticException.class,() -> { int i = 10 / 2; //The assertion is executed //int j = 10 / 0; // The assertion is not executed at this time },"Business logic is running normally???"); } }
If the code execution int i = 10 / 2 runs correctly, no arithmetexception exception will occur, so the assertThrows assertion will be executed at this time. Otherwise, it will not be executed.
3.5 timeout assertion
Junit5 also provides Assertions.assertTimeout() to set the timeout for the test method.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } @DisplayName("Test timeout assertion") @Test public void testTimeoutAssertions() { assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(2000)); } }
3.6 rapid failure
The test fails directly through the fail method.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } @DisplayName("Rapid failure") @Test public void testFailAssertions() { System.out.println(1); System.out.println(2); fail("Go straight...."); System.out.println(3); System.out.println(4); } }
4. Preconditions
Preconditions 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. Preconditions can be regarded as the premise of test method execution. When the premise is not met, there is no need to continue execution.
package com.szh.boot; import org.junit.jupiter.api.*; import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.junit.jupiter.api.Assertions.*; /** * */ //@SpringBootTest @DisplayName("Junit5 Functional test class") public class TestJunit { @BeforeEach public void testBeforeEach() { System.out.println("The test is about to begin...."); } @AfterEach public void testAfterEach() { System.out.println("The test is over...."); } @BeforeAll public static void testBeforeAll() { System.out.println("All tests are about to begin...."); } @AfterAll public static void testAfterAll() { System.out.println("All tests are over...."); } //----------------------------Preconditions---------------------------- @DisplayName("Test preconditions") @Test public void testAssumptions() { Assumptions.assumeTrue(true, "The result is not true...."); System.out.println(111); } }
Set Assumptions.assumeTrue(false, "the result is not true..."); Change true in to false because true= False, so if the precondition is not met, the execution of the test method will be terminated directly.
5. Nested test
JUnit 5 can implement Nested tests through internal classes and @ Nested annotations in Java, so as to better organize relevant test methods together. You can use @ BeforeEach and @ AfterEach annotations in internal classes, and there is no limit to the nesting level.
package com.szh.boot; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.util.EmptyStackException; import java.util.Stack; import static org.junit.jupiter.api.Assertions.*; /** * In nested tests, the outer @ Test cannot drive the inner @ Before(After)Each/All and other methods to run in advance / after * The inner @ Test can drive the outer @ Before(After)Each/All and other methods to run in advance / later */ @DisplayName("A stack") public class TestingAStackDemo { Stack<Object> stack; @Test @DisplayName("is instantiated with new Stack()") void isInstantiatedWithNew() { new Stack<>(); } @Nested @DisplayName("when new") class WhenNew { @BeforeEach void createNewStack() { stack = new Stack<>(); } @Test @DisplayName("is empty") void isEmpty() { assertTrue(stack.isEmpty()); } @Test @DisplayName("throws EmptyStackException when popped") void throwsExceptionWhenPopped() { assertThrows(EmptyStackException.class, stack::pop); } @Test @DisplayName("throws EmptyStackException when peeked") void throwsExceptionWhenPeeked() { assertThrows(EmptyStackException.class, stack::peek); } @Nested @DisplayName("after pushing an element") class AfterPushing { String anElement = "an element"; @BeforeEach void pushAnElement() { stack.push(anElement); } @Test @DisplayName("it is no longer empty") void isNotEmpty() { assertFalse(stack.isEmpty()); } @Test @DisplayName("returns the element when popped and is empty") void returnElementWhenPopped() { assertEquals(anElement, stack.pop()); assertTrue(stack.isEmpty()); } @Test @DisplayName("returns the element when peeked but remains not empty") void returnElementWhenPeeked() { assertEquals(anElement, stack.peek()); assertFalse(stack.isEmpty()); } } } }
6. 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.
@ValueSource: Specifies the input parameter source for parametric testing. It supports eight basic classes, String type and Class type
@NullSource: indicates that a null input parameter is provided for the parameterized test
@EnumSource: indicates that an enumeration input parameter is provided for the parameterized test
@CsvFileSource: read the contents of the specified CSV file as the parametric test input parameter
@MethodSource: means to read the return value of the specified method as the parameterized test input parameter (note that the method return needs to be a stream)
7.JUnit4 → Junit5
https://junit.org/junit5/docs/current/user-guide/#migrating-from-junit4
The following changes should be noted during migration:
- Annotations are in the org.junit.jupiter.api package, assertions are in the org.junit.jupiter.api.Assertions class, and preconditions are in the org.junit.jupiter.api.Assumptions class.
- Replace @ Before and @ After with @ BeforeEach and @ AfterEach.
- Replace @ BeforeClass and @ AfterClass with @ BeforeAll and @ AfterAll.
- Replace @ Ignore with @ Disabled.
- Replace @ Category with @ Tag.
- Replace @ RunWith, @ Rule and @ ClassRule with @ ExtendWith.