Junit5: Support @TempDir tags in extensions

Created on 18 Feb 2019  路  9Comments  路  Source: junit-team/junit5

I have a bunch of tests that require me to initialise a temporary directory with some files, so that the methods in my tests can read and write to it.
Currently, the solution is to implement a BeforeAll/BeforeEach tag and initialise the temporary directory for each test class.
As this is a common routine, it would be more reuse friendly if we could write an extension that encapsulates this routine, and which only needs to receive an input of a Path to initialise a temporary directory with all the files.
I envision the extension to look something like

DirectoryInitExtension.java

public class DirectoryInitExtension implements BeforeEachCallback {

    private Path TEST_DATA_FOLDER;

    @TempDir
    public Path tempDir;

    public static DirectoryInitExtension initializeWith(Path folder) {
        return new DirectoryInitExtension(folder);
    }

    @Override
    public void beforeEach(ExtensionContext extensionContext) throws IOException {
        // Copy files from TEST_DATA_FOLDER into tempDir, then return this tempDir as part of the test context.
    }
}

MainTest.java

 @RegisterExtension
    public static DirectoryResetExtension tempDir = DirectoryResetExtension.initializeWith(TEST_DATA_FOLDER);

However, as I soon learnt (thanks to a fast response here), TempDirs are not supported in extensions.

I wonder if it is a possibility to enable the same capabilities of TempDirs in extensions.

Deliverables

  • [ ] Support TempDir in extensions? (Sorry, not too sure what to write here)
Jupiter team discussion extensions

Most helpful comment

Team Decision: Store Path in ExtensionContext.Store and make it available to other extensions via a well-known key/namespace combination.

All 9 comments

Extensions should not depend on other extension - not even on those built-in extensions that Jupiter provides.

Why don't you just use Files.createTempDirectory(...) in beforeEach and delete that directory in a afterEach callback? The latter may profit from exposing the "delete directory tree recursively" helper by Jupiter in some way.

A generic resource supplier SPI (https://github.com/junit-team/junit5/pull/1672) could help here, methinks. With such an extension in place, test authors could write:

@Test
void test4711(@New(TestDataFolder.class) Path path) { ... }

Will try to implement and publish a spike in JUnit Pioneer, soon. Meanwhile, if you're running your tests on Java 11, you may try https://github.com/sormuras/brahms#resource-manager-extension

Extensions should not depend on other extension - not even on those built-in extensions that Jupiter provides.

That's typically true; however, there are times when interactions between extensions is desirable. For example, the SpringExtension has a public static ApplicationContext getApplicationContext(ExtensionContext) method that is used internally by the SpringExtension but intentionally public for use by other extensions.

Another option for inter-extension communication/collaboration is the ExtensionContext.Store. The Store was originally designed with such inter-extension collaboration in mind.

However, for the use case in this issue, allowing fields in an extension to be annotated with @TempDir is not something we would want to do, for many reasons but especially since extensions should typically not be stateful.

Why don't you just use Files.createTempDirectory(...) in beforeEach and delete that directory in a afterEach callback?

I agree that managing such a temporary directory within the custom extension itself is the more robust approach, especially since the lifecycle of the temporary directory should be managed by that extension and not by the built-in TempDirectory extension.

The latter may profit from exposing the "delete directory tree recursively" helper by Jupiter in some way.

Yes, we could consider publishing that in something like a new IoSupport utility class.

Related to #1604. Tentatively slated for 5.5 M2 for _team discussion_

Team Decision: Store Path in ExtensionContext.Store and make it available to other extensions via a well-known key/namespace combination.

@marcphilipp I was trying to think around how that would work for other extensions. Aren't ParameterResolver extensions one of the "last" callbacks that get invoked? For example, if a BeforeEachCallback gets invoked and tried to retrieve from the ExtensionContext.Store with the well-known namespace key, wouldn't it see that it isn't in the store? How does ordering/dependent extensions hook into it? Or, is TempDir a little bit special since it is built into Jupiter?

@mkobit, those are valid questions.

If an extension attempts to look up the temp dir in the Store before it has been created (by the internal TempDirectory extension), it simply won't be there.

So we probably need to rethink this and provide an on-demand mechanism for acquiring access to the temp dir. One of the potential problems with that is determining what the scope/lifecycle of the temp dir should be.

@junit-team/junit-lambda, looks like we need to brainstorm a bit more on this topic.

Indeed.

I've been trying to think of some possible ways for clients to use this, as it seems related to my request at https://github.com/junit-team/junit5/issues/1802.

Maybe there could be a notion of _"extending the Extension.Store"_ so that other extensions that request values out of the store can be produced on demand? Everything I think of keeps seeming like the eventuality of dependency injection, which could be a lot more complexity then is desired.

Was this page helpful?
0 / 5 - 0 ratings