JUnit 5 API中有几个扩展点可用。如何测试扩展实现
例如:
AfterEachCallback
AfterAllCallback
AfterTestExecutionCallback
BeforeAllCallback
BeforeEachCallback
BeforeTestExecutionCallback
ExecutionCondition
ParameterResolver
TestExecutionExceptionHandler
TestInstancePostProcessor
TestTemplateInvocationContextProvider
几乎所有这些扩展采取某种形式的ExtensionContext
,其提供了访问test class,test method,test instance,能力publish report entries,store values和其他功能。它们中的很多直接在类实例上运行
大多数实现可能会使用reflection using ReflectionSupport
和annotation discovery using AnnotationSupport
的某些组合,这些都是静态方法,这使得它们很难模拟。
现在,假设我已经编写了ExecutionCondition
的实现,或者实现了BeforeEachCallback
和AfterEachCallback
的扩展 - 我如何有效地测试它们?
我想我可以模拟ExtensionContext
,但这似乎不是一种有效行使不同代码路径的好方法。
JUnit内部有ExecutionEventRecorder
,它与JupiterTestEngine
implementation for execution (which is @API(status=INTERNAL)
)一起用于选择和执行测试,然后perform assertions on the resulting events。
我可以使用相同的模式,因为JupiterTestEngine
上的public
可见性。
这里有几个具体的例子来证明:
的
ExecutionCondition
,使基于系统属性测试。这可以通过一些思考来测试,并使用TestInfo
中的@BeforeEach
或@AfterEach
风格进行测试,但在并行测试执行到来时,处理和可能出现的问题似乎更加复杂。这个例子展示了一个例子,“我如何提供真实生活ExtensionContext
并对结果进行断言”。@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @ExtendWith(SystemPropertyCondition.class) public @interface SystemProperty { String key(); String value(); } public class SystemPropertyCondition implements ExecutionCondition { @Override public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) { return context.getTestMethod() .flatMap(element -> AnnotationSupport.findAnnotation(element, SystemProperty.class)) .map(systemProperty -> { if (systemProperty.value().equals(System.getProperty(systemProperty.key()))) { return ConditionEvaluationResult.enabled("Property is available"); } else { return ConditionEvaluationResult.disabled("Property not equal"); } }) .orElseGet(() -> ConditionEvaluationResult.enabled("No annotations")); } }
的
ExecutionCondition
,仅在一周的提供每天运行。如何注入测试时钟?如果条件是预期结果,你将如何测试?我猜想可能会将某些状态/实现置于ExtensionContext.Store
之间,从而允许在扩展之间传递某种状态(例如,也可以使用BeforeEachCallback
,这可能是正确的路径,但这将取决于扩展的顺序我不相信BeforeEachCallback
在ExecutionCondition
之前被调用,所以路径可能不是正确的。这个例子显示了“我如何注入依赖关系”,并且也显示了与前面提供ExtensionContext
示例相同的问题和断言该结果。@ExtendWith(RunOnDayCondition.class) public @interface RunOnDay { DayOfWeek[] value(); } final class RunOnDayCondition implements ExecutionCondition { private static final ConditionEvaluationResult DEFAULT = disabled( RunOnDay.class + " is not present" ); @Override public ConditionEvaluationResult evaluateExecutionCondition(final ExtensionContext context) { return context.getElement() .flatMap(annotatedElement -> findAnnotation(annotatedElement, RunOnDay.class)) .map(RunOnDay::value) .map(RunOnDayCondition::evaluateIfRunningOnDay) .orElse(DEFAULT); } private static ConditionEvaluationResult evaluateIfRunningOnDay(final DayOfWeek[] days) { // TODO: How would you inject a test clock? final DayOfWeek currentDay = LocalDate.now().getDayOfWeek(); final boolean runningInday = Stream.of(days).anyMatch(currentDay::equals); if (runningInday) { return enabled("Current day is " + currentDay + ", in the specified days of " + Arrays.toString(days)); } else { return disabled("Current day is " + currentDay + ", not in the specified days of " + Arrays.toString(days)); } } }
,设置了一个临时目录,提供它作为一个参数,然后在测试后清除它的扩展,这将使用
ParameterResolver
,BeforeEachCallback
,AfterEachCallback
和ExtensionContext.Store
。这个例子表明一个扩展实现可以使用多个扩展点,并且可以利用该商店来跟踪状态。
扩展测试的自定义测试引擎是否是正确的方法?
那么,如何测试各种扩展实现而不依赖于内部API?