Jupiter Advanced
- ParameterResolver
- DynamicTests
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
-
- 实现ParameterResolver接口
-
- @ExtendWith(CustomParameterResolver.class)扩展参数解析
-
- @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
宿主测试类被标注的@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
- 定义Repeat注解,并注册TestTemplateInvocationContextProvider扩展
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@TestTemplate
@ExtendWith(DawnRepeatTestTemplateInvocationContextProvider.class)
public @interface DawnRepeat {
int value();
}
- 定义Repeate信息
@RequiredArgsConstructor
@ToString
@Data
public class DawnRepeatInfo {
private final int current;
private final int total;
}
- 定义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.
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
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
- 定义方法参数值数据源
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")
);
}
}
- 测试
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
- 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);
}
}
- 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
基于配置开启并发执行单元测试
- 为方便测试,添加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
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
<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")));
}
}
}