Jupiter Advanced

58 阅读5分钟

Jupiter Advanced

ParameterResolver

In former JUnit versions, test constructors and methods were not allowed to have parameters. One of the major changes in JUnit 5 is that both test constructors and methods are now allowed to include parameters. This feature enables the dependency injection for constructors and methods.

If a test constructor or a method annotated with @Test, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, or @AfterAll accepts a parameter, that parameter is resolved at runtime by a resolver (object with parent class ParameterResolver). There are three built-in resolvers registered automatically in JUnit 5: TestInfoParameterResolver, and RepetitionInfoParameterResolver, TestReporterParameterResolver.

TestInfo

@DisplayName("TestInfoParameterResolverClass")
public class TestInfoParameterResolver {

    // @DisplayName 无用,testInfo参数展示的是测试类的DisplayName
    @DisplayName("TestInfoParameterResolver#@BeforeAll")
    @BeforeAll
    @Tag("init")
    static void init(TestInfo testInfo){
        System.out.println();
        System.out.println("TestInfo#getDisplayName=>"+testInfo.getDisplayName());
        System.out.println("TestInfo#getTestClass=>"+testInfo.getTestClass());
        System.out.println("TestInfo#getTestMethod=>"+testInfo.getTestMethod().orElse(null));
        System.out.println("TestInfo#getTags=>"+testInfo.getTags());
        System.out.println();
    }


    // @DisplayName 无用,testInfo参数展示的是测试方法的DisplayName
    @DisplayName("TestInfoParameterResolver#@BeforeEach")
    @BeforeEach
    @Tag("setup")
    void setup(TestInfo testInfo){
        System.out.println();
        System.out.println("TestInfo#getDisplayName=>"+testInfo.getDisplayName());
        System.out.println("TestInfo#getTestClass=>"+testInfo.getTestClass());
        System.out.println("TestInfo#getTestMethod=>"+testInfo.getTestMethod().orElse(null));
        System.out.println("TestInfo#getTags=>"+testInfo.getTags());
        System.out.println();
    }

    @DisplayName("TestInfoParameterResolver#@Test")
    @Test
    @Tag("testMethod")
    void testMethod(TestInfo testInfo){
        System.out.println();
        System.out.println("TestInfo#getDisplayName=>"+testInfo.getDisplayName());
        System.out.println("TestInfo#getTestClass=>"+testInfo.getTestClass());
        System.out.println("TestInfo#getTestMethod=>"+testInfo.getTestMethod().orElse(null));
        System.out.println("TestInfo#getTags=>"+testInfo.getTags());
        System.out.println();
    }
}

运行结果如下:

TestInfo#getDisplayName=>TestInfoParameterResolverClass
TestInfo#getTestClass=>Optional[class com.dawn.foundation.parameter.TestInfoParameterResolver]
TestInfo#getTestMethod=>null
TestInfo#getTags=>[]


TestInfo#getDisplayName=>TestInfoParameterResolver#@Test
TestInfo#getTestClass=>Optional[class com.dawn.foundation.parameter.TestInfoParameterResolver]
TestInfo#getTestMethod=>void com.dawn.foundation.parameter.TestInfoParameterResolver.testMethod(org.junit.jupiter.api.TestInfo)
TestInfo#getTags=>[testMethod]


TestInfo#getDisplayName=>TestInfoParameterResolver#@Test
TestInfo#getTestClass=>Optional[class com.dawn.foundation.parameter.TestInfoParameterResolver]
TestInfo#getTestMethod=>void com.dawn.foundation.parameter.TestInfoParameterResolver.testMethod(org.junit.jupiter.api.TestInfo)
TestInfo#getTags=>[testMethod]

RepetitionInfo


public class TestRepetitionInfoParameterResolver {

    @DisplayName("[RepeatTest]")
    @RepeatedTest(value = 2,name = RepeatedTest.LONG_DISPLAY_NAME)
    void repeatTest(RepetitionInfo repetitionInfo, TestInfo testInfo){
        System.out.println();
        System.out.println("RepetitionInfo#getCurrentRepetition=>"+repetitionInfo.getCurrentRepetition());
        System.out.println("RepetitionInfo#getTotalRepetitions=>"+repetitionInfo.getTotalRepetitions());
        System.out.println();
    }
}

运行结果:

RepetitionInfo#getCurrentRepetition=>1
RepetitionInfo#getTotalRepetitions=>2

TestReporter

/**
 * maven和idea等工具会显示test执行过程中发布的信息
 */
public class TestReporterParameterResolver {

    @BeforeEach
    void setup(TestInfo testInfo, RepetitionInfo repetitionInfo, TestReporter testReporter) {
        testReporter.publishEntry("DawnDisplayName", testInfo.getDisplayName());
        testReporter.publishEntry("DawnTotalRepetitions", String.valueOf(repetitionInfo.getTotalRepetitions()));
    }

    @DisplayName("TestReporterParameterResolverTest#@Test")
    @RepeatedTest(2)
    @Tag("testMethod")
    void testMethod(){
        System.out.println(">>testMethod");
    }
}

运行结果:

timestamp = 2024-05-27T22:04:09.056716200, DawnDisplayName = repetition 1 of 2
timestamp = 2024-05-27T22:04:09.061702400, DawnTotalRepetitions = 2
>>testMethod
timestamp = 2024-05-27T22:04:09.104587300, DawnDisplayName = repetition 2 of 2
timestamp = 2024-05-27T22:04:09.106581600, DawnTotalRepetitions = 2
>>testMethod

Custom Parameter Resolver

    1. 实现ParameterResolver接口
    1. @ExtendWith(CustomParameterResolver.class)扩展参数解析
    1. @ExtendWith既可以作用于测试类,也可以作用于测试方法
public interface RandomStringValue {

  @Target(ElementType.METHOD)
  @Retention(RetentionPolicy.RUNTIME)
  @Documented
  @interface Random {
    String[] value() default {};
  }

  String getValue();


  @RequiredArgsConstructor
  class DefaultRandomStringValue implements RandomStringValue {
    
    private final ExtensionContext extensionContext;

    @Override
    public String getValue() {
      final var testMethod = this.extensionContext.getRequiredTestMethod();
      final var random = testMethod.getDeclaredAnnotation(Random.class);
      final var data = random.value();
      if (data.length == 0) {
        return "N/A";
      }
      return data[ThreadLocalRandom.current().nextInt(data.length)];
    }
  }
}
public class CustomParameterResolver implements ParameterResolver {

  @Override
  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
          throws ParameterResolutionException {
    return extensionContext.getRequiredTestMethod().isAnnotationPresent(RandomStringValue.Random.class);
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
          throws ParameterResolutionException {
    return new RandomStringValue.DefaultRandomStringValue(extensionContext);
  }
}
@ExtendWith(CustomParameterResolver.class)
class CustomParameterResolverTest {

  @RandomStringValue.Random(
          {"Hello", "World", "Java", "Junit", "Jupiter"}
  )
  @RepeatedTest(5)
  void customParameterTest(RandomStringValue randomValue) {
    System.out.println(randomValue.getValue());
  }
}

DynamicTests

junit.org/junit5/docs…

宿主测试类被标注的@TestFactory方法依然支持@BeforeEach,@AfterEach,LifecycleCallBack,而生成的动态测试方法是不支持的。

public class DynamicTestsTest {


    @TestFactory
    public List<DynamicTest> dynamicTestList() {
        List<Integer> userIds = queryUserId(0);
        return userIds.stream().map(index -> DynamicTest.dynamicTest("dynamic test of " + index,
                () -> MatcherAssert.assertThat("error", true)
        )).collect(Collectors.toList());
    }


    @TestFactory
    public Stream<DynamicTest> dynamicTestStream() {
        List<Integer> userIds = queryUserId(0);
        return userIds.stream().map(index -> DynamicTest.dynamicTest("dynamic test of " + index,
                () -> MatcherAssert.assertThat("error", true)
        ));
    }

    List<Integer> queryUserId(int departmentId) {
        if (departmentId == 0) {
            return List.of(0, 1, 2, 3, 4);
        }
        return List.of(5, 6, 7, 8);
    }


    /**
     * DynamicTest extends DynamicNode
     * DynamicContainer extends DynamicNode
     *
     * DynamicContainer 有一个孩子属性,是DynamicNode的子类(可以是DynamicContainer也可以是DynamicTest)
     * public class DynamicContainer extends DynamicNode {
     *     private final Stream<? extends DynamicNode> children;
     * }
     *
     * @return
     */

    @ExtendWith(JupiterInstanceLifecycle.DawnBeforeEachCallback.class)
    @TestFactory
    DynamicNode dynamicTestsWithContainer() {
        return DynamicContainer.dynamicContainer("dynamic Container【1】", Stream.of(
                DynamicTest.dynamicTest("dynamic test【1-1】", () -> Assertions.assertTrue(true)),
                DynamicContainer.dynamicContainer("dynamic container【1-1】", Stream.of(
                        DynamicTest.dynamicTest("dynamic test【1-1-1】", () -> Assertions.assertTrue(true)),
                        DynamicTest.dynamicTest("dynamic test【1-1-1】", () -> Assertions.assertTrue(true))
                ))
        ));
    }

    @BeforeEach
    void beforeEach(TestInfo testInfo) {
        System.out.println("@BeforeEach->" + testInfo);
    }

    @AfterEach
    void afterEach(TestInfo testInfo) {
        System.out.println("@AfterEach->" + testInfo);
    }

}

Test Template

A @TestTemplate method is not a regular test case but a template for test cases. Method annotated like this will be invoked multiple times, depending on the invocation context returned by the registered providers. Thus, test templates are used together with a registered TestTemplateInvocationContextProvider extension.

  • RepeatedTest
  • Parameterized tests

Understand Test Template will helpful understand Parameterized tests

Custom Repeat Test

  1. 定义Repeat注解,并注册TestTemplateInvocationContextProvider扩展
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestTemplate
@ExtendWith(DawnRepeatTestTemplateInvocationContextProvider.class)
public @interface DawnRepeat {
    int value();
}
  1. 定义Repeate信息
@RequiredArgsConstructor
@ToString
@Data
public class DawnRepeatInfo {
    private final int current;
    private final int total;
}
  1. 定义DawnRepeatTestTemplateInvocationContextProvider
public class DawnRepeatTestTemplateInvocationContextProvider implements TestTemplateInvocationContextProvider {
    @Override
    public boolean supportsTestTemplate(ExtensionContext extensionContext) {
        return extensionContext.getRequiredTestMethod().isAnnotationPresent(DawnRepeat.class);
    }

    @Override
    public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext extensionContext) {
        DawnRepeat repeat = extensionContext.getRequiredTestMethod().getAnnotation(DawnRepeat.class);
        // 返回每次执行方法时需要传递给测试方法的TestTemplateInvocationContext
        return IntStream.rangeClosed(1, repeat.value()).mapToObj(index -> {
            DawnRepeatInfo repeatInfo = new DawnRepeatInfo(index, repeat.value());
            return getTestTemplateInvocationContext(repeatInfo);
        });
    }

    private TestTemplateInvocationContext getTestTemplateInvocationContext(DawnRepeatInfo repeatInfo) {
        return new TestTemplateInvocationContext() {
            @Override
            public String getDisplayName(int invocationIndex) {
                return "DawnRepeatTest:[" + invocationIndex + "]";
            }

            @Override
            public List<Extension> getAdditionalExtensions() {
                ParameterResolver parameterResolver = new ParameterResolver() {

                    @Override
                    public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
                        return extensionContext.getRequiredTestMethod().isAnnotationPresent(DawnRepeat.class);
                    }

                    @Override
                    public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
                        if (parameterContext.getParameter().getType().equals(DawnRepeatInfo.class)) {
                            return repeatInfo;
                        }
                        if(parameterContext.getParameter().getType().equals(String.class)){
                            return repeatInfo.toString();
                        }
                        return null;
                    }
                };
                return Arrays.asList(parameterResolver);
            }
        };
    }
}

测试

public class DawnRepeatTest {

    @DawnRepeat(2)
    public void repeatTest(DawnRepeatInfo dawnRepeatInfo, String str) {
        System.out.println("dawnRepeatInfo: " + dawnRepeatInfo);
        System.out.println("str: " + str);
    }
}

Parameterized tests

Parameterized tests are a special kinds of tests in which the data input is injected in the test in order to reuse the same test logic.

junit.org/junit5/docs…

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-params</artifactId>
    <version>${junit.jupiter.version}</version>
    <scope>test</scope>
</dependency>

image.png

ValueSource、EnumSource、CsvSource、CsvFileSource

public class ParameterizedValueSourceTest {

    @Nested
    @DisplayName("Value source Test")
    class NestedValueSource {

        @ParameterizedTest
        @ValueSource(strings = {"Java", "Junit"})
        void stringValueSource(String value) {
            System.out.println(value);
        }

        @ParameterizedTest
        @ValueSource(ints = {1, 2})
        void intValueSource(int value) {
            System.out.println(value);
        }
    }

    @Nested
    @DisplayName("Enum Value Source Test")
    class NestedEnumValueSource {

        @ParameterizedTest
        @EnumSource(TimeUnit.class)
        void enumSource(TimeUnit unit) {
            System.out.println(unit);
        }
    }

    @Nested
    @DisplayName("Csv Value Source Test")
    class NestedCvsValueSource {

        @ParameterizedTest
        @CsvSource({
                "Java,17",
                "Junit,5",
                "Jupiter's,5.7"
        })
        void csvValueSource(String value, double version) {
            System.out.println(value);
            System.out.println(version);
        }
    }

    @Nested
    @DisplayName("Csv file source Test")
    class NestedCsvFileSource {

        @ParameterizedTest
        @CsvFileSource(resources = {"/a.csv", "/b.csv"})
        void testCsvFileSource(String value, double version) {
            System.out.println(value + ":" + version);
        }
    }
}

Method Parameterized Value Source

  1. 定义方法参数值数据源
public class MethodValueSourceProvider {

    public static Stream<String> stringProvider() {
        return Stream.of("java", "junit");
    }

    public static IntStream intProvider() {
        return IntStream.of(4, 7);
    }

    public static Stream<User> userProvider() {
        return Stream.of(
                new User("z001", "zhuoya"),
                new User("z002", "dawn")
        );
    }

    public static Stream<Arguments> argumentsProvider() {
        return Stream.of(
                Arguments.arguments("c001", "zhuoya"),
                Arguments.arguments("c002", "dawn")
        );
    }
}
  1. 测试
public class ParameterizedMethodValueSourceTest {

    /**
     * 如果是同一个类中的方法可省略包名+类名@MethodSource("stringProvider")
     *
     * @param value
     */
    @ParameterizedTest
    @MethodSource("com.dawn.advanced.parameterized.MethodValueSourceProvider#stringProvider")
    public void stringMethodSource(String value) {
        System.out.println(value);
    }

    @ParameterizedTest
    @MethodSource("com.dawn.advanced.parameterized.MethodValueSourceProvider#intProvider")
    public void intMethodSource(Integer value) {
        System.out.println(value);
    }

    @ParameterizedTest
    @MethodSource("com.dawn.advanced.parameterized.MethodValueSourceProvider#userProvider")
    public void intMethodSource(User user) {
        System.out.println(user);
    }

    @ParameterizedTest
    @MethodSource("com.dawn.advanced.parameterized.MethodValueSourceProvider#argumentsProvider")
    public void intMethodSource(String userId, String userName) {
        System.out.println(userId + ":" + userName);
    }
}

Arguments Provider

public class ArgumentsProviderTest {

    @ParameterizedTest
    @ArgumentsSources({
            @ArgumentsSource(DawnArgumentsProvider1.class),
            @ArgumentsSource(DawnArgumentsProvider2.class)
    })
    void argumentProviderSource(String value, int id) {
        System.out.println(value + ":" + id);
    }

    static class DawnArgumentsProvider1 implements ArgumentsProvider {
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
            return Stream.of(
                    Arguments.of("Java", 30),
                    Arguments.of("Junit", 130)
            );
        }
    }

    static class DawnArgumentsProvider2 implements ArgumentsProvider {
        @Override
        public Stream<? extends Arguments> provideArguments(ExtensionContext extensionContext) throws Exception {
            return Stream.of(
                    Arguments.of("cc", 11),
                    Arguments.of("dd", 22)
            );
        }
    }
}

Argument Converter

  1. implicit convert
@Nested
@DisplayName("Convert By Implicit")
class NestedImplicitConvert {

    @ParameterizedTest
    @ValueSource(strings = {"true", "false"})
    void booleanImplicitConvert(boolean value) {
        System.out.println(value);
    }

    @ParameterizedTest
    @ValueSource(strings = {"DAYS", "SECONDS"})
    void enumImplicitConvert(TimeUnit value) {
        System.out.println(value);
    }

}
  1. explicit convert
public class DawnArgumentsConverter extends SimpleArgumentConverter {

    @Override
    protected Object convert(Object o, Class<?> aClass) throws ArgumentConversionException {
        return o.toString();
    }
}
@Nested
@DisplayName("Convert By Explicit")
class NestedExplicitConvert {

    @ParameterizedTest
    @EnumSource(TimeUnit.class)
    void enumToString(@ConvertWith(DawnArgumentsConverter.class) String value) {
        System.out.println(value);
    }

    @ParameterizedTest
    @MethodSource("com.dawn.advanced.parameterized.MethodValueSourceProvider#userProvider")
    void userToString(@ConvertWith(DawnArgumentsConverter.class) String value) {
        System.out.println(value);
    }

}

parallel execution

junit.org/junit5/docs… By default, JUnit Jupiter tests are run sequentially in a single thread. Running tests in parallel — for example, to speed up execution — is available as an opt-in feature since version 5.3.

To enable parallel execution, set the junit.jupiter.execution.parallel.enabled configuration parameter to true

Configuration file name junit-platform.properties

  • all tests in parallel
    • junit.jupiter.execution.parallel.enabled = true
    • junit.jupiter.execution.parallel.mode.default = concurrent
  • execute top-level classes in parallel but methods in same thread
    • junit.jupiter.execution.parallel.enabled = true
    • junit.jupiter.execution.parallel.mode.default = same_thread
    • junit.jupiter.execution.parallel.mode.classes.default = concurrent
  • execute top-level classes in sequentially but their methods in parallel
    • junit.jupiter.execution.parallel.enabled = true
    • junit.jupiter.execution.parallel.mode.default = concurrent
    • junit.jupiter.execution.parallel.mode.classes.default = same_thread

image.png

基于配置开启并发执行单元测试

  • 为方便测试,添加groups配置,目的在测试并发时过滤出想要执行的单元测试
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.1.2</version>
<configuration>
    <groups>ParallelExecution1|ParallelExecution2</groups>
</configuration>

开启并发测试

junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.mode.classes.default=same_thread

编写测试类并执行单元测试

@Tag("ParallelExecution1")
public class ParallelExecution1 {

  @Test
  void test1A() {
    System.out.println(Thread.currentThread().getName() + " => test1A");
  }

  @Test
  void test1B() {
    System.out.println(Thread.currentThread().getName() + " => test1B");
  }
}

@Tag("ParallelExecution2")
public class ParallelExecution2 {

  @Test
  void test2A() {
    System.out.println(Thread.currentThread().getName() + " => test2A");
  }

  @Test
  void test2B() {
    System.out.println(Thread.currentThread().getName() + " => test2B");
  }
}

基于注解并发执行单元测试

注解的方式会覆盖配置的方式

@Execution(ExecutionMode.CONCURRENT)
public class ParallelExecutionWithAnnotation {

    @Execution(ExecutionMode.CONCURRENT)
    @Test
    void test3A() {
        System.out.println(Thread.currentThread().getName() + " => Annotation3A");
    }

    @Execution(ExecutionMode.CONCURRENT)
    @Test
    void test3B() {
        System.out.println(Thread.currentThread().getName() + " => Annotation3B");
    }

    @Execution(ExecutionMode.CONCURRENT)
    @Test
    void test3C() {
        System.out.println(Thread.currentThread().getName() + " => Annotation3C");
    }
}

DynamicTests

@Execution(ExecutionMode.CONCURRENT)
@TestFactory
Stream<DynamicTest> testDynamicTests() {
    return IntStream.rangeClosed(1, 10).mapToObj(i ->
            dynamicTest("Display Name:" + i, () ->
            {
                assertTrue(i > 0);
                System.out.println(Thread.currentThread().getName() + " =>" + i);
            })
    );
}

ResourceLock

并发可能导致的资源竞争,读写冲突,写写冲突,读读不冲突

@Execution(ExecutionMode.CONCURRENT)
public class ParallelExecutionWithResourceLock {

    private final static String RESOURCE_LOCK = "ParallelExecutionWithResourceLockKey";

    @ResourceLock(value = RESOURCE_LOCK, mode = ResourceAccessMode.READ)
    @Test
    void testA() throws InterruptedException {
        System.out.println(LocalDateTime.now() + "=>" + Thread.currentThread().getName() + " => testA");
        TimeUnit.SECONDS.sleep(5);
    }

    @ResourceLock(value = RESOURCE_LOCK, mode = ResourceAccessMode.READ)
    @Test
    void testB() throws InterruptedException {
        System.out.println(LocalDateTime.now() + "=>" + Thread.currentThread().getName() + " => testB");
        TimeUnit.SECONDS.sleep(5);
    }

    @ResourceLock(value = RESOURCE_LOCK, mode = ResourceAccessMode.READ_WRITE)
    @Test
    void testC() throws InterruptedException {
        System.out.println(LocalDateTime.now() + "=>" + Thread.currentThread().getName() + " => testC");
        TimeUnit.SECONDS.sleep(1);
    }

}

Jupiter tests in JUnit4

Junit4 中运行 Junit5的代码

   <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit.jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
       <version>${junit.jupiter.version}</version>
       <scope>test</scope>
     </dependency>
     <dependency>
        <groupId>org.junit.platform</groupId>
        <artifactId>junit-platform-runner</artifactId>
        <version>${junit.platform.version}</version>
        <scope>test</scope>
     </dependency>

编写测试类

@DisplayName("Jupiter test in junit 4")
@RunWith(JUnitPlatform.class)
class JupiterTest {

  @BeforeAll
  static void init() {
    System.out.println("@BeforeAll");
  }

  @BeforeEach
  void setUp(TestInfo testInfo) {
    System.out.println("@BeforeEach=>" + testInfo);
  }

  @Tag("jupiter")
  @RepeatedTest(4)
  @DisplayName("jupiter test function")
  public void testRepeat(RepetitionInfo repetitionInfo, TestInfo testInfo) {
    System.out.println(repetitionInfo + "<=>" + testInfo);
  }
}

Junit4 tests in Jupiter

在junit5 中运行 junit4的代码

 <dependency>
     <groupId>org.junit.vintage</groupId>
     <artifactId>junit-vintage-engine</artifactId>
     <version>${junit-5-jupiter-version}</version>
     <scope>test</scope>
 </dependency>

 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13.1</version>
     <scope>test</scope>
</dependency>

编写测试代码运行

public class Junit4Test
{

  @BeforeClass
  public static void init()
  {
    System.out.println("@BeforeClass");
  }

  @Before
  public void setUp()
  {
    System.out.println("@Before");
  }

  @Test
  public void junitTest()
  {
    System.out.println("===>junitTest");
  }
}

junit5 中运行 junit4 的Rule,需要使用@EnableRuleMigrationSupport

<dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter-migrationsupport</artifactId>
   <version>${junit.jupiter.version}</version>
   <scope>test</scope>
</dependency>
@EnableRuleMigrationSupport
class JunitRuleTest {

  @Rule
  TemporaryFolder temporaryFolder = new TemporaryFolder();

  @BeforeEach
  void setup() throws IOException {
    temporaryFolder.create();
  }

  @Test
  void test() {
    System.out.println("Temporary folder: " +
            temporaryFolder.getRoot());
  }

  @AfterEach
  void teardown() {
    temporaryFolder.delete();
  }
}

Integration Other Freamwork

Integrate With Mockito

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-junit-jupiter</artifactId>
    <version>3.11.2</version>
    <scope>test</scope>
</dependency>
@ExtendWith(MockitoExtension.class)
public class MockitoIntegrationTest {

    @Mock
    private List<String> list;

    @Spy
    private List<String> spyList = new ArrayList<>();

    @Test
    void testMockList() {
        when(list.get(0)).thenReturn("java").thenReturn("junit").thenReturn("jupiter");
        assertEquals(list.get(0), "java");
        assertEquals(list.get(0), "junit");
        assertEquals(list.get(0), "jupiter");
        assertEquals(list.get(0), "jupiter");
    }

    @Test
    void testMockList(@Mock List<String> list) {
        when(list.get(0)).thenReturn("java").thenReturn("junit").thenReturn("jupiter");
        assertEquals(list.get(0), "java");
        assertEquals(list.get(0), "junit");
        assertEquals(list.get(0), "jupiter");
        assertEquals(list.get(0), "jupiter");
    }

    @Test
    void testSpyList() {
        assertEquals(0, spyList.size());
        doReturn("java").doReturn("junit").doReturn("jupiter").when(spyList).get(0);
        assertAll(
                () -> assertEquals("java", spyList.get(0)),
                () -> assertEquals("junit", spyList.get(0)),
                () -> assertEquals("jupiter", spyList.get(0)),
                () -> assertEquals("jupiter", spyList.get(0))
        );
    }

    @InjectMocks
    private TestService testService;

    @Mock
    private TestRepository testRepository;

    @Test
    void testService() {
        when(testRepository.insert()).thenReturn(true);
        assertTrue(testService.create());
    }

    static class TestService {
        private final TestRepository testRepository;

        TestService(TestRepository testRepository) {
            this.testRepository = testRepository;
        }

        public boolean create() {
            return this.testRepository.insert();
        }
    }

    static class TestRepository {
        public boolean insert() {
            throw new UnsupportedOperationException();
        }
    }
}

Integrate With Powermock

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-module-junit4</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-api-mockito2</artifactId>
    <version>${powermock.version}</version>
    <scope>test</scope>
</dependency>
@RunWith(PowerMockRunner.class)
public class PowerMockIntegrationTest {

  @Test
  @PrepareForTest(DawnInvoke.class)
  public void testStaticMethod() {
    PowerMockito.mockStatic(DawnInvoke.class);
    PowerMockito.when(DawnInvoke.staticMethod()).thenReturn(true);
    assertTrue(new InvokeService().invokeStaticMethod());
  }


  @PrepareForTest(DawnInvoke.class)
  @Test
  public void testNonStaticMethod() throws Exception {
    final var dawnInvoke = PowerMockito.mock(DawnInvoke.class);
    PowerMockito.whenNew(DawnInvoke.class).withNoArguments().thenReturn(dawnInvoke);
    PowerMockito.doReturn(true).when(dawnInvoke).nonStaticMethod();
    assertTrue(new InvokeService().invokeNonStaticMethod());
  }

}

Integrate With Cucumber

添加依赖

<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java8</artifactId>
    <version>6.10.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>6.10.4</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>io.cucumber</groupId>
    <artifactId>cucumber-junit-platform-engine</artifactId>
    <version>6.10.4</version>
    <scope>test</scope>
</dependency>

插件配置

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>3.1.2</version>
    <configuration>
        <includes>
            <include>**/*Runner.java</include>
            <include>**/*Test.java</include>
        </includes>
        <properties>
            <configurationParameters>
                cucumber.plugin= pretty,json:target/cucumber-report/cucumber-report.json,junit:target/cucumber-junit.xml,html:target/site/cucumber-pretty.html
                cucumber.publish.quiet=true
                cucumber.publish.enabled=false
                cucumber.features=classpath:com/dawn/cucumber
                cucumber.glue=com.dawn
            </configurationParameters>
        </properties>
    </configuration>
    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit-jupiter.version}</version>
        </dependency>
    </dependencies>
</plugin>

创建feature image.png

public class CucumberStepDefs implements En {

    private int remainCount;

    public CucumberStepDefs() {
        Given("there are {int} available rooms in the hotel", (Integer remainCount) ->
        {
            this.remainCount = remainCount;
        });
        When("I book {int} rooms for 3 nights each", (Integer bookCount) ->
        {
            this.remainCount -= bookCount;
        });

        Then("there should be {int} rooms remaining in the hotel", (Integer remaining) ->
        {
            assertEquals(remaining, remainCount);
        });
    }
}
@Cucumber
public class CucumberRunner {
}

Integrate With Selenium

image.png

<dependency>
        <groupId>io.github.bonigarcia</groupId>
        <artifactId>selenium-jupiter</artifactId>
        <version>3.4.0</version>
        <scope>test</scope>
</dependency>

Integrate With SpringBoot

添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <executions>
                <execution>
                    <goals>
                        <goal>repackage</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.0.0-M5</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.jupiter</groupId>
                    <artifactId>junit-jupiter-engine</artifactId>
                    <version>${junit-jupiter.version}</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
@SpringBootTest
@AutoConfigureMockMvc
public class UserApiTest {

    @Nested
    class NestedWithoutMock {
        @Test
        void testFindPhoneBrands(@Autowired MockMvc mockMvc) throws Exception {

            mockMvc.perform(get("/user/list"))
                    .andExpect(status().isOk())
                    .andDo(print())
                    .andExpect(jsonPath("$", Matchers.hasSize(2)))
                    .andExpect(jsonPath("$[0].userNo", Matchers.is("001")));
        }
    }

    @Nested
    class NestedWithMock {
        @MockBean
        private UserService userService;

        @Test
        void testFindPhoneBrands(@Autowired MockMvc mockMvc) throws Exception {
            when(userService.getUsers()).thenReturn(List.of(new User("003", "c003")));
            mockMvc.perform(get("/user/list"))
                    .andExpect(status().isOk())
                    .andDo(print())
                    .andExpect(jsonPath("$", Matchers.hasSize(1)))
                    .andExpect(jsonPath("$[0].userNo", Matchers.is("003")));
        }
    }
}