Skip to content

Commit f93b1fc

Browse files
authored
Support @TempDir constructor injection for Java record classes (#4279)
The `@TempDir` annotation on a constructor parameter is copied to the generated `final` instance field. This caused it top be picked up by `TempDirectory` for instance field injection which then failed because the field was `final`. This change now checks if a test class is an instance of a record type and skips instance field injection. Since records cannot have any instance fields besides the ones generated from their constructor, there's no need to inject values into any of them.
1 parent e6446ff commit f93b1fc

File tree

4 files changed

+54
-1
lines changed

4 files changed

+54
-1
lines changed

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/extension/TempDirectory.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;
2020
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
2121
import static org.junit.platform.commons.support.ReflectionSupport.makeAccessible;
22+
import static org.junit.platform.commons.util.ReflectionUtils.isRecordObject;
2223

2324
import java.io.File;
2425
import java.io.IOException;
@@ -135,7 +136,9 @@ private void injectStaticFields(ExtensionContext context, Class<?> testClass) {
135136
}
136137

137138
private void injectInstanceFields(ExtensionContext context, Object instance) {
138-
injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic);
139+
if (!isRecordObject(instance)) {
140+
injectFields(context, instance, instance.getClass(), ModifierSupport::isNotStatic);
141+
}
139142
}
140143

141144
private void injectFields(ExtensionContext context, Object testInstance, Class<?> testClass,

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,25 @@ public static boolean isInnerClass(Class<?> clazz) {
365365
return !isStatic(clazz) && clazz.isMemberClass();
366366
}
367367

368+
/**
369+
* {@return whether the supplied {@code object} is an instance of a record class}
370+
* @since 1.12
371+
*/
372+
@API(status = INTERNAL, since = "1.12")
373+
public static boolean isRecordObject(Object object) {
374+
return object != null && isRecordClass(object.getClass());
375+
}
376+
377+
/**
378+
* {@return whether the supplied {@code clazz} is a record class}
379+
* @since 1.12
380+
*/
381+
@API(status = INTERNAL, since = "1.12")
382+
public static boolean isRecordClass(Class<?> clazz) {
383+
Class<?> superclass = clazz.getSuperclass();
384+
return superclass != null && "java.lang.Record".equals(superclass.getName());
385+
}
386+
368387
/**
369388
* Determine if the return type of the supplied method is primitive {@code void}.
370389
*

jupiter-tests/src/test/java/org/junit/jupiter/engine/extension/TempDirectoryPerDeclarationTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ void resolvesSeparateTempDirsForEachAnnotationDeclaration(TestInstance.Lifecycle
164164
assertThat(testATempDirs).doesNotContainEntry("afterEach2", testBTempDirs.get("afterEach2"));
165165
}
166166

167+
@Test
168+
void supportsConstructorInjectionOnRecords() {
169+
executeTestsForClass(TempDirRecordTestCase.class).testEvents()//
170+
.assertStatistics(stats -> stats.started(1).succeeded(1));
171+
}
172+
167173
@Test
168174
@DisplayName("does not prevent constructor parameter resolution")
169175
void tempDirectoryDoesNotPreventConstructorParameterResolution() {
@@ -1527,4 +1533,12 @@ void test(@SuppressWarnings("unused") @TempDir Path tempDir) {
15271533

15281534
}
15291535

1536+
@SuppressWarnings("JUnitMalformedDeclaration")
1537+
record TempDirRecordTestCase(@TempDir Path tempDir) {
1538+
@Test
1539+
void shouldExists() {
1540+
assertTrue(Files.exists(tempDir));
1541+
}
1542+
}
1543+
15301544
}

platform-tests/src/test/java/org/junit/platform/commons/util/ReflectionUtilsTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,23 @@ void getInterfaceMethodIfPossible() throws Exception {
285285
assertThat(interfaceMethod.getDeclaringClass()).isEqualTo(Closeable.class);
286286
}
287287

288+
@Test
289+
void isRecordObject() {
290+
assertTrue(ReflectionUtils.isRecordObject(new SomeRecord(1)));
291+
assertFalse(ReflectionUtils.isRecordObject(new ClassWithOneCustomConstructor("")));
292+
assertFalse(ReflectionUtils.isRecordObject(null));
293+
}
294+
295+
@Test
296+
void isRecordClass() {
297+
assertTrue(ReflectionUtils.isRecordClass(SomeRecord.class));
298+
assertFalse(ReflectionUtils.isRecordClass(ClassWithOneCustomConstructor.class));
299+
assertFalse(ReflectionUtils.isRecordClass(Object.class));
300+
}
301+
302+
record SomeRecord(int n) {
303+
}
304+
288305
static class ClassWithVoidAndNonVoidMethods {
289306

290307
void voidMethod() {

0 commit comments

Comments
 (0)