解决 JUnit 5 中的 ParameterResolutionException 异常
1、概览
JUnit 5 引入了一些强大的功能,包括支持参数化测试(parameterized testing)。编写参数化测试可以节省大量时间,而且在许多情况下,只需简单组合注解就能启用参数化测试。
然而,错误配置可能会导致难以调试的异常,例如:ParameterResolutionException
。
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter ...
本文将带你了解如何解决 ParameterResolutionException
异常。
2、JUnit 5 的 ParameterResolver
异常信息中表示缺少 ParameterResolver
。
这是 JUnit 5 中引入的 ParameterResolver
接口,允许开发人员扩展 JUnit 的基本功能,编写可接受任何类型参数的测试。
来看一个简单的 ParameterResolver
实现:
public class FooParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
// 是否支持参数类型的逻辑
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
// 参数解析逻辑
}
}
该类有两个主要方法:
supportsParameter()
:确定是否支持参数类型resolveParameter()
:返回执行测试的参数
因为 ParameterResolutionsException
是在没有 ParameterResolver
实现的情况下抛出的,所以先不关心实现细节。首先来看看异常的一些潜在原因。
3、ParameterResolutionException
ParameterResolutionException
可能很难调试,尤其是对那些不太熟悉参数化测试的人来说。
首先,义一个简单的 Book
类:
public class Book {
private String title;
private String author;
// Get、Set 方法省略
}
编写一些单元测试,验证 Book
的 title
值。
@Test
void givenWutheringHeights_whenCheckingTitleLength_thenTitleIsPopulated() {
Book wuthering = new Book("Wuthering Heights", "Charlotte Bronte");
assertThat(wuthering.getTitle().length()).isGreaterThan(0);
}
@Test
void givenJaneEyre_whenCheckingTitleLength_thenTitleIsPopulated() {
Book jane = new Book("Jane Eyre", "Charlotte Bronte");
assertThat(wuthering.getTitle().length()).isGreaterThan(0);
}
不难看出,这两个测试基本上在做同一件事:设置 Book
的 title
和检查长度。我们可以将它们合并为一个参数化测试,从而简化测试。
接下来看看这种重构可能出错的几种情况。
3.1、向 @Test 方法传递参数
一种非常快速的方法,我们可能会认为将参数传递给带有 @Test
注解的方法就足够了:
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
代码可以编译和运行,但再仔细想想,就会发现这些参数是从哪里来的。在运行这个示例时,抛出了一个异常:
org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [java.lang.String arg0] in method ...
JUnit 无法知道向测试方法传递哪些参数。
继续重构单元测试,看看可能导致 ParameterResolutionException
的另一个原因。
3.2、注解冲突
如前所述,可以使用 ParameterResolver
来提供缺失的参数,但让我们从更简单的 value source 开始。由于有两个值(title
和 author
),可以使用 CsvSource
为测试提供这些值。
此外,还缺少一个关键的注解:@ParameterizedTest
。这个注解告诉 JUnit 测试是参数化的,并且有测试值注入到其中。
尝试重构:
@ParameterizedTest
@CsvSource({"Wuthering Heights, Charlotte Bronte", "Jane Eyre, Charlotte Bronte"})
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
这似乎是合理的。然而,当运行单元测试时,你就会发现一些有趣的现象:两次测试运行通过,第三次测试运行失败。仔细观察,还可以发现了一个警告:
WARNING: Possible configuration error: method [...] resulted in multiple TestDescriptors [org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor, org.junit.jupiter.engine.descriptor.TestTemplateTestDescriptor].
This is typically the result of annotating a method with multiple competing annotations such as @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, etc.
由于添加了冲突的注解,无意中创建了多个 TestDescriptors
。这意味着 JUnit 仍在运行测试的原始 @Test
版本和新的参数化测试。
只需移除 @Test
注解就能解决这个问题。
3.3、使用 ParameterResolver
前面介绍了一个实现 ParameterResolver
的简单示例。现在有了一个可用的测试,让我们引入一个 BookParameterResolver
:
public class BookParameterResolver implements ParameterResolver {
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Book.class;
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return parameterContext.getParameter().getType() == Book.class
? new Book("Wuthering Heights", "Charlotte Bronte")
: null;
}
}
这是一个简单的示例,只返回一个用于测试的 Book
实例。现在我们有了一个 ParameterResolver
来提供测试值。
回到第一个示例中的测试。再次尝试一下:
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
Book book = new Book(title, author);
assertThat(book.getTitle().length()).isGreaterThan(0);
assertThat(book.getAuthor().length()).isGreaterThan(0);
}
但在运行该测试时发现同样的异常依然存在。但原因略有不同:既然有了 ParameterResolver
,仍需告诉 JUnit 如何使用它。
只需在包含测试方法的外层类中添加 @ExtendWith
注解即可:
@ExtendWith(BookParameterResolver.class)
public class BookUnitTest {
@Test
void givenTitleAndAuthor_whenCreatingBook_thenFieldsArePopulated(String title, String author) {
// 测试内容
}
// 其他单元测试
}
再次运行,测试成功执行。
4、总结
本文介绍了在 JUnit 5 中导致 ParameterResolutionException
异常的原因以及解决办法。
Ref:https://www.baeldung.com/junit-5-parameterresolutionexception