Skip to content

Commit

Permalink
Add constructor injection support for TempDir
Browse files Browse the repository at this point in the history
This was made possible by lifting the `ParameterResolver` limitation for
accessing the test-scoped `ExtensionContext` for test class constructors
in #3445.
  • Loading branch information
marcphilipp committed Oct 8, 2024
1 parent 1a35c56 commit d6b7466
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ JUnit repository on GitHub.
`ExtensionContext` while instantiating the test instance.
The behavior enabled by the annotation is expected to eventually become the default in
future versions of JUnit Jupiter.
* `@TempDir` is now supported on test class constructors.


[[release-notes-5.12.0-M1-junit-vintage]]
Expand Down
11 changes: 4 additions & 7 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3003,7 +3003,8 @@ The built-in `{TempDirectory}` extension is used to create and clean up a tempor
directory for an individual test or all tests in a test class. It is registered by
default. To use it, annotate a non-final, unassigned field of type `java.nio.file.Path` or
`java.io.File` with `{TempDir}` or add a parameter of type `java.nio.file.Path` or
`java.io.File` annotated with `@TempDir` to a lifecycle method or test method.
`java.io.File` annotated with `@TempDir` to a test class constructor, lifecycle method, or
test method.

For example, the following test declares a parameter annotated with `@TempDir` for a
single test method, creates and writes to a file in the temporary directory, and checks
Expand All @@ -3028,14 +3029,10 @@ entire test class or method (depending on which level the annotation is used), y
the `junit.jupiter.tempdir.scope` configuration parameter to `per_context`. However,
please note that this option is deprecated and will be removed in a future release.

`@TempDir` is not supported on constructor parameters. If you wish to retain a single
reference to a temp directory across lifecycle methods and the current test method, please
use field injection by annotating an instance field with `@TempDir`.

The following example stores a _shared_ temporary directory in a `static` field. This
allows the same `sharedTempDir` to be used in all lifecycle methods and test methods of
the test class. For better isolation, you should use an instance field so that each test
method uses a separate directory.
the test class. For better isolation, you should use an instance field or constructor
injection so that each test method uses a separate directory.

[source,java,indent=0]
.A test class that shares a temporary directory across test methods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.nio.file.DirectoryNotEmptyException;
Expand All @@ -45,12 +44,12 @@
import org.junit.jupiter.api.extension.AnnotatedElementContext;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.EnableTestScopedConstructorContext;
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.io.CleanupMode;
import org.junit.jupiter.api.io.TempDir;
Expand Down Expand Up @@ -78,6 +77,7 @@
* @see TempDir
* @see Files#createTempDirectory
*/
@EnableTestScopedConstructorContext
class TempDirectory implements BeforeAllCallback, BeforeEachCallback, ParameterResolver {

static final Namespace NAMESPACE = Namespace.create(TempDirectory.class);
Expand Down Expand Up @@ -161,12 +161,7 @@ private void injectFields(ExtensionContext context, Object testInstance, Class<?
*/
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
boolean annotated = parameterContext.isAnnotated(TempDir.class);
if (annotated && parameterContext.getDeclaringExecutable() instanceof Constructor) {
throw new ParameterResolutionException(
"@TempDir is not supported on constructor parameters. Please use field injection instead.");
}
return annotated;
return parameterContext.isAnnotated(TempDir.class);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ void resetStaticVariables() {
BaseSharedTempDirParameterInjectionTestCase.tempDir = null;
BaseSeparateTempDirsFieldInjectionTestCase.tempDirs.clear();
BaseSeparateTempDirsParameterInjectionTestCase.tempDirs.clear();
BaseConstructorInjectionTestCase.tempDirs.clear();
}

@Test
Expand Down Expand Up @@ -217,6 +218,17 @@ void resolvesSharedTempDirWhenAnnotationIsUsedOnBeforeAllMethodParameterWithTest
AnnotationOnBeforeAllMethodParameterWithTestInstancePerClassTestCase.class);
}

@Test
@DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)")
@Order(25)
void resolvesSharedTempDirWhenAnnotationIsUsedOnConstructorWithTestInstancePerClass() {
var results = executeTestsForClass(SharedTempDirsConstructorInjectionPerClassTestCase.class);

results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2));
assertThat(BaseConstructorInjectionTestCase.tempDirs.getFirst()).doesNotExist();
assertThat(BaseConstructorInjectionTestCase.tempDirs.getLast()).doesNotExist();
}

private void assertSharedTempDirForFieldInjection(
Class<? extends BaseSharedTempDirFieldInjectionTestCase> testClass) {

Expand Down Expand Up @@ -296,6 +308,17 @@ void resolvesSeparateTempDirWhenAnnotationIsUsedOnAfterAllMethodParameterOnly()
assertThat(AnnotationOnAfterAllMethodParameterTestCase.secondTempDir).isNotNull().doesNotExist();
}

@Test
@DisplayName("when @TempDir is used on constructor parameter")
@Order(32)
void resolvesSeparateTempDirsWhenAnnotationIsUsedOnConstructorWithTestInstancePerMethod() {
var results = executeTestsForClass(SeparateTempDirsConstructorInjectionPerMethodTestCase.class);

results.testEvents().assertStatistics(stats -> stats.started(2).failed(0).succeeded(2));
assertThat(BaseConstructorInjectionTestCase.tempDirs.getFirst()).doesNotExist();
assertThat(BaseConstructorInjectionTestCase.tempDirs.getLast()).doesNotExist();
}

}

@Nested
Expand Down Expand Up @@ -370,26 +393,6 @@ void onlySupportsParametersOfTypePathAndFile() {
// @formatter:on
}

@Test
@DisplayName("when @TempDir is used on constructor parameter")
@Order(30)
void doesNotSupportTempDirAnnotationOnConstructorParameter() {
var results = executeTestsForClass(AnnotationOnConstructorParameterTestCase.class);

assertSingleFailedTest(results, ParameterResolutionException.class,
"@TempDir is not supported on constructor parameters. Please use field injection instead.");
}

@Test
@DisplayName("when @TempDir is used on constructor parameter with @TestInstance(PER_CLASS)")
@Order(31)
void doesNotSupportTempDirAnnotationOnConstructorParameterWithTestInstancePerClass() {
var results = executeTestsForClass(AnnotationOnConstructorParameterWithTestInstancePerClassTestCase.class);

assertSingleFailedContainer(results, ParameterResolutionException.class,
"@TempDir is not supported on constructor parameters. Please use field injection instead.");
}

@Test
@DisplayName("when non-default @TempDir factory is set")
@Order(32)
Expand Down Expand Up @@ -693,26 +696,75 @@ static void check(Path tempDir) {

}

static class AnnotationOnConstructorParameterTestCase {
static class BaseConstructorInjectionTestCase {

AnnotationOnConstructorParameterTestCase(@SuppressWarnings("unused") @TempDir Path tempDir) {
// never called
static final Deque<Path> tempDirs = new LinkedList<>();

private final Path tempDir;

BaseConstructorInjectionTestCase(Path tempDir) {
this.tempDir = tempDir;
}

@Test
void test() {
// never called
void test1(@TempDir Path tempDir, TestInfo testInfo) throws Exception {
check(tempDir);
writeFile(tempDir, testInfo);
}

@Test
void test2(TestInfo testInfo, @TempDir Path tempDir) throws Exception {
check(tempDir);
writeFile(tempDir, testInfo);
}

@AfterEach
void afterEach(@TempDir Path tempDir) {
check(tempDir);
}

void check(Path tempDir) {
assertThat(tempDirs.getLast())//
.isNotNull()//
.isSameAs(tempDir)//
.isSameAs(this.tempDir);
assertTrue(Files.exists(tempDir));
}

}

static class SeparateTempDirsConstructorInjectionPerMethodTestCase extends BaseConstructorInjectionTestCase {

SeparateTempDirsConstructorInjectionPerMethodTestCase(@TempDir Path tempDir) {
super(tempDir);
}

@BeforeEach
void beforeEach(@TempDir Path tempDir) {
for (Path dir : tempDirs) {
assertThat(dir).doesNotExist();
}
assertThat(tempDirs).doesNotContain(tempDir);
tempDirs.add(tempDir);
check(tempDir);
}
}

@TestInstance(PER_CLASS)
static class AnnotationOnConstructorParameterWithTestInstancePerClassTestCase
extends AnnotationOnConstructorParameterTestCase {
static class SharedTempDirsConstructorInjectionPerClassTestCase extends BaseConstructorInjectionTestCase {

AnnotationOnConstructorParameterWithTestInstancePerClassTestCase(@TempDir Path tempDir) {
SharedTempDirsConstructorInjectionPerClassTestCase(@TempDir Path tempDir) {
super(tempDir);
}

@BeforeEach
void beforeEach(@TempDir Path tempDir) {
for (Path dir : tempDirs) {
assertThat(dir).isSameAs(tempDir).exists();
}
tempDirs.add(tempDir);
check(tempDir);
}
}

static class NonDefaultFactoryTestCase {
Expand Down Expand Up @@ -803,7 +855,6 @@ static void afterAll(@TempDir Path tempDir) {
}

static class BaseSeparateTempDirsFieldInjectionTestCase {

static final Deque<Path> tempDirs = new LinkedList<>();

@TempDir
Expand Down
Loading

0 comments on commit d6b7466

Please # to comment.