Chapter 7 SpringBoot unit test

1 Junit5

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 to run on Junit Platform.
JUnit Vintage: since JUint has been developed for many years, in order to take care of old projects, JUnit Vintage provides JUnit 4.0 compatible services x,Junit3. X test engine.

be careful

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>

2 Junit5 common notes

● @ Test: indicates the 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
● @ parametrizedtest: the expression method is parametric test, which will be described in detail below
● @ RepeatedTest: the expression method can be executed repeatedly, which will be described in detail below
● @ DisplayName: set the display name for the test class or test method
● @ BeforeEach: means to execute before each unit test
● @ AfterEach: means to execute after each unit test
● @ BeforeAll: means to execute before all unit tests
● @ AfterAll: 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 JUnit4
● @ Timeout: indicates that the test method will return an error if it runs beyond the specified time
● @ ExtendWith: provide extension class references for test classes or test methods

import org.junit.jupiter.api.Test; //Note that the Test annotation of jupiter is used here!!


public class TestDemo {

  @Test
  @DisplayName("First test")
  public void firstTest() {
      System.out.println("hello world");
  }

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 org junit. jupiter. api. Static method of assertions.
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.

method explain
assertEquals Determines whether two objects or two primitive types are equal
assertNotEquals Judge whether two objects or two original 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

3.2 array assertion

Use the assertArrayEquals method to determine whether two objects or arrays of original types are equal.

3.3 combined assertions

The assertAll method accepts multiple org junit. jupiter. api. As the assertions to be verified, the instance of the executable functional interface can easily provide these assertions through lambda expressions.

@Test
@DisplayName("assert all")
public void all() {
 assertAll("Math",
    () -> assertEquals(2, 1 + 1),
    () -> assertTrue(1 > 0)
 );
}

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 way of assertions Assertthrows() can be used in conjunction with functional programming.

@Test
@DisplayName("Abnormal test")
public void exceptionTest() {
    ArithmeticException exception = Assertions.assertThrows(
           //Throw assertion exception
            ArithmeticException.class, () -> System.out.println(1 % 0));

}

3.5 timeout assertion

Junit5 also provides assertions Asserttimeout() sets a timeout for the test method.

@Test
@DisplayName("Timeout tests ")
public void timeoutTest() {
    //If the test method takes more than 1s, it will be abnormal
    Assertions.assertTimeout(Duration.ofMillis(1000), () -> Thread.sleep(500));
}

3.6 rapid failure

The test fails directly through the fail method.

@Test
@DisplayName("fail")
public void shouldFail() {
 fail("This should fail");
}

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.

@DisplayName("Preconditions")
public class AssumptionsTest {
 private final String environment = "DEV";
 
 @Test
 @DisplayName("simple")
 public void simpleAssume() {
    assumeTrue(Objects.equals(this.environment, "DEV"));
    assumeFalse(() -> Objects.equals(this.environment, "PROD"));
 }
 
 @Test
 @DisplayName("assume then do")
 public void assumeThenDo() {
    assumingThat(
       Objects.equals(this.environment, "DEV"),
       () -> System.out.println("In DEV")
    );
 }
}

assumeTrue and assumFalse ensure that the given condition is true or false. If the condition is not met, the test execution will be terminated. The parameter assemingthat is the Boolean value representing the condition and the implementation object of the corresponding Executable interface. The Executable object will be executed only when the conditions are met; When the conditions are not met, the test execution does not terminate.

5 nesting 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.

@DisplayName("A stack")
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());
            }
        }
    }
}

be careful

The test of the inner layer can drive the methods such as Before(After)Each/All of the outer layer to run in advance / later. The test of the outer layer cannot drive the methods such as Before(After)Each/All of the inner layer.

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 to conduct multiple unit tests without adding a unit test every time a parameter is added, which saves 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: means to read the contents of the specified CSV file as the parameterized 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)

If the parametric test can only specify ordinary input parameters, it can not reach the point that makes me feel amazing. What makes me really feel his strength is that he can support all kinds of external participation. For example, CSV,YML,JSON files and even the return value of methods can also be used as input parameters. You only need to implement the ArgumentsProvider interface, and any external file can be used as its input parameter.

@ParameterizedTest
@ValueSource(strings = {"one", "two", "three"})
@DisplayName("Parametric test 1")
public void parameterizedTest1(String string) {
    System.out.println(string);
    Assertions.assertTrue(StringUtils.isNotBlank(string));
}


@ParameterizedTest
@MethodSource("method")    //Specify method name
@DisplayName("Method source parameters")
public void testWithExplicitLocalMethodSource(String name) {
    System.out.println(name);
    Assertions.assertNotNull(name);
}

static Stream<String> method() {
    return Stream.of("apple", "banana");
}

7 Junit4 migration guide

The following changes should be noted during migration:
● notes at org junit. jupiter. In the API package, the assertions are in org junit. jupiter. api. In the assertions class, the preconditions are in org junit. jupiter. api. In the 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.

Keywords: Spring

Added by mduran on Thu, 03 Feb 2022 11:46:20 +0200