JUnit 5 中的 ExternalResource 和 TemporaryFolder 等价物是什么?

What is the equivalent of ExternalResource and TemporaryFolder in JUnit 5?

根据 JUnit 5 User Guide,JUnit Jupiter 为某些 JUnit 4 规则提供向后兼容性以协助迁移。

As stated above, JUnit Jupiter does not and will not support JUnit 4 rules natively. The JUnit team realizes, however, that many organizations, especially large ones, are likely to have large JUnit 4 codebases including custom rules. To serve these organizations and enable a gradual migration path the JUnit team has decided to support a selection of JUnit 4 rules verbatim within JUnit Jupiter.

指南继续说其中一条规则是 ExternalResource, which is a parent for TemporaryFolder

但是,不幸的是,该指南没有继续说明迁移路径是什么,或者对于那些编写新的 JUnit 5 测试的等效路径是什么。那我们应该怎么用呢?

相关文档仍在制作中 - 请参阅 pull request #660

Interesting article by author of TemporaryFolderExtension for JUnit5

his code repo on github

JUnit5.0.0 现已正式发布,所以我们希望他们将注意力转移到使实验性产品准备就绪。

与此同时,TemporaryFolder 规则似乎仍适用于 JUnit5 docs

使用这个:

@EnableRuleMigrationSupport
public class MyJUnit5Test {

还有这个:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-migrationsupport</artifactId>
    <version>5.0.0</version>
</dependency>

据我所知,从 ExternalResource 到 JUnit5 中的等价物没有一对一的映射。这些概念只是不适合。在 JUnit4 中,ExternalResource 基本上给你一个 before 和一个 after 回调,但在规则内,你无法控制 beforeafter 实际上是什么方法。您可以将它与 @Rule@ClassRule.

一起使用

在 JUnit5 中,扩展定义为挂钩特定 extension points,因此 'when' 定义明确。

概念上的另一个区别是,您可以在 JUnit4 规则中有一个状态,但您的 JUnit5 扩展不应该有任何状态。相反,所有状态都应该转到 execution context.

然而,这是我提出的一个选项,其中 beforeafter 与每个测试方法相关:

public abstract class ExternalResourceExtension 
  implements BeforeTestExecutionCallback, AfterTestExecutionCallback {
    @Override
    public void beforeTestExecution(ExtensionContext context) throws Exception {
        before(context);
    }

    @Override
    public void afterTestExecution(ExtensionContext context) throws Exception {
        after(context);
    }

    protected abstract void before(ExtensionContext context);

    protected abstract void after(ExtensionContext context);
}

JUnit 5.4 带有一个内置扩展来处理测试中的临时目录。

@org.junit.jupiter.api.io.TempDir 注释可用于注释 class 字段或生命周期中的参数(例如 @BeforeEach)或类型 FilePath

import org.junit.jupiter.api.io.TempDir;

@Test
void writesContentToFile(@TempDir Path tempDir) throws IOException {
    // arrange
    Path output = tempDir
            .resolve("output.txt");

    // act
    fileWriter.writeTo(output.toString(), "test");

    // assert
    assertAll(
            () -> assertTrue(Files.exists(output)),
            () -> assertLinesMatch(List.of("test"), Files.readAllLines(output))
    );
}

您可以在我的博客 post 中阅读更多相关信息,您可以在其中找到更多关于使用此内置扩展的示例:https://blog.codeleak.pl/2019/03/temporary-directories-in-junit-5-tests.html

临时文件夹现在有@TempDir的解决方法。但是,一般来说 ExternalResource 背后的想法是什么?可能是针对模拟数据库、模拟 HTTP 连接或您要为其添加支持的其他自定义资源?

答案,事实证明你可以使用 @RegisterExtension 注释来实现非常相似的东西。

使用示例:

/**
 * This is my resource shared across all tests
 */
@RegisterExtension
static final MyResourceExtension MY_RESOURCE = new MyResourceExtension();

/**
 * This is my per test resource
 */
@RegisterExtension
final MyResourceExtension myResource = new MyResourceExtension();

@Test
void test() {
    MY_RESOURCE.doStuff();
    myResource.doStuff();
}

下面是 MyResourceExtension 的基本脚手架:

public class MyResourceExtension implements BeforeAllCallback, AfterAllCallback,
        BeforeEachCallback, AfterEachCallback {

    private SomeResource someResource;

    private int referenceCount;

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        beforeEach(context);
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        afterEach(context);
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        if (++referenceCount == 1) {
            // Do stuff in preparation
            this.someResource = ...;
        }
    }

    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        if (--referenceCount == 0) {
            // Do stuff to clean up
            this.someResource.close();
            this.someResource = null;
        }
    }

    public void doStuff() {
        return this.someResource.fooBar();
    }

}

你当然可以把这一切都包装成一个抽象的 class 并让 MyResourceExtension 只实现 protected void before()protected void after() 或类似的东西,如果那是你的事,但为简洁起见,我将其省略。