From 6f4244f8d4a5640714a59bc8739a01ed8a11b5d2 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sat, 24 Jun 2017 13:51:48 +0100 Subject: [PATCH] Add @TestInstance(Lifecycle.PER_CLASS) support for @Nested test classes Issue: #419 --- .../descriptor/ClassTestDescriptor.java | 44 +++--- .../descriptor/NestedClassTestDescriptor.java | 19 ++- .../engine/TestInstanceLifecycleTests.java | 129 +++++++++++++++--- 3 files changed, 145 insertions(+), 47 deletions(-) diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java index 9c68931c2052..9146859134a6 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java @@ -68,7 +68,7 @@ public class ClassTestDescriptor extends JupiterTestDescriptor { private static final ExecutableInvoker executableInvoker = new ExecutableInvoker(); private final Class testClass; - private final Lifecycle lifecycle; + protected final Lifecycle lifecycle; private final List beforeAllMethods; private final List afterAllMethods; @@ -179,7 +179,15 @@ public void after(JupiterEngineExecutionContext context) throws Exception { protected TestInstanceProvider testInstanceProvider(JupiterEngineExecutionContext parentExecutionContext, ExtensionRegistry registry, ExtensionContext extensionContext) { - return new LifecycleAwareTestInstanceProvider(this.testClass, registry, extensionContext); + TestInstanceProvider testInstanceProvider = childExtensionRegistry -> { + Constructor constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); + Object instance = executableInvoker.invoke(constructor, extensionContext, + childExtensionRegistry.orElse(registry)); + invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(registry), extensionContext); + return instance; + }; + + return new LifecycleAwareDelegatingTestInstanceProvider(testInstanceProvider, this.lifecycle); } protected void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry, @@ -281,43 +289,29 @@ private void invokeMethodInTestExtensionContext(Method method, TestExtensionCont executableInvoker.invoke(method, instance, context, registry); } - private final class LifecycleAwareTestInstanceProvider implements TestInstanceProvider { + protected static final class LifecycleAwareDelegatingTestInstanceProvider implements TestInstanceProvider { - private final Class testClass; - private final ExtensionRegistry registry; - private final ExtensionContext extensionContext; + private final TestInstanceProvider testInstanceProvider; + private final Lifecycle lifecycle; private Object testInstance; - LifecycleAwareTestInstanceProvider(Class testClass, ExtensionRegistry registry, - ExtensionContext extensionContext) { - - this.testClass = testClass; - this.registry = registry; - this.extensionContext = extensionContext; + LifecycleAwareDelegatingTestInstanceProvider(TestInstanceProvider testInstanceProvider, Lifecycle lifecycle) { + this.testInstanceProvider = testInstanceProvider; + this.lifecycle = lifecycle; } @Override public Object getTestInstance(Optional childExtensionRegistry) { - if (ClassTestDescriptor.this.lifecycle == Lifecycle.PER_METHOD) { - return createTestInstance(childExtensionRegistry); + if (this.lifecycle == Lifecycle.PER_METHOD) { + return this.testInstanceProvider.getTestInstance(childExtensionRegistry); } // else Lifecycle.PER_CLASS if (this.testInstance == null) { - this.testInstance = createTestInstance(childExtensionRegistry); + this.testInstance = this.testInstanceProvider.getTestInstance(childExtensionRegistry); } return this.testInstance; } - - private Object createTestInstance(Optional childExtensionRegistry) { - Constructor constructor = ReflectionUtils.getDeclaredConstructor(this.testClass); - Object instance = executableInvoker.invoke(constructor, this.extensionContext, - childExtensionRegistry.orElse(this.registry)); - invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(this.registry), - this.extensionContext); - return instance; - } - } private static TestInstance.Lifecycle getTestInstanceLifecycle(Class testClass) { diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java index a15221efcf32..40bd4f3fb983 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java @@ -61,17 +61,24 @@ public final Set getTags() { protected TestInstanceProvider testInstanceProvider(JupiterEngineExecutionContext parentExecutionContext, ExtensionRegistry registry, ExtensionContext extensionContext) { - return childExtensionRegistry -> { + TestInstanceProvider testInstanceProvider = childExtensionRegistry -> { // Extensions registered for nested classes and below are not to be used for instantiating outer classes Optional childExtensionRegistryForOuterInstance = Optional.empty(); - Object outerInstance = parentExecutionContext.getTestInstanceProvider().getTestInstance( - childExtensionRegistryForOuterInstance); + + // @formatter:off + Object outerInstance = parentExecutionContext.getTestInstanceProvider().getTestInstance(childExtensionRegistryForOuterInstance); + + ExtensionRegistry registryToUse = childExtensionRegistry.orElse(registry); Constructor constructor = ReflectionUtils.getDeclaredConstructor(getTestClass()); - Object instance = executableInvoker.invoke(constructor, outerInstance, extensionContext, - childExtensionRegistry.orElse(registry)); - invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(registry), extensionContext); + Object instance = executableInvoker.invoke(constructor, outerInstance, extensionContext, registryToUse); + + invokeTestInstancePostProcessors(instance, registryToUse, extensionContext); + return instance; + // @formatter:on }; + + return new LifecycleAwareDelegatingTestInstanceProvider(testInstanceProvider, this.lifecycle); } } diff --git a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java index a751a08d99cf..f0e736406d7b 100644 --- a/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java +++ b/junit-jupiter-engine/src/test/java/org/junit/jupiter/engine/TestInstanceLifecycleTests.java @@ -18,25 +18,22 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.platform.engine.test.event.ExecutionEventRecorder; -import org.junit.platform.runner.JUnitPlatform; -import org.junit.runner.RunWith; /** * Tests for {@link TestInstance @TestInstance} lifecycle support. * * @since 5.0 */ -@RunWith(JUnitPlatform.class) -public - class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests { private static int instanceCount; + private static int nestedInstanceCount; private static int beforeAllCount; private static int afterAllCount; private static int beforeEachCount; @@ -45,6 +42,7 @@ class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests { @BeforeEach void init() { instanceCount = 0; + nestedInstanceCount = 0; beforeAllCount = 0; afterAllCount = 0; beforeEachCount = 0; @@ -53,36 +51,47 @@ void init() { @Test void instancePerMethod() { - performAssertions(InstancePerMethodTestCase.class, 2, 1, 1); + performAssertions(InstancePerMethodTestCase.class, 2, 2, 0, 1); } @Test void instancePerClass() { - performAssertions(InstancePerClassTestCase.class, 1, 2, 2); + performAssertions(InstancePerClassTestCase.class, 2, 1, 0, 2); } - private void performAssertions(Class testClass, int expectedInstanceCount, int expectedBeforeAllCount, - int expectedAfterAllCount) { + @Test + void instancePerMethodWithNestedTestClass() { + performAssertions(InstancePerMethodOuterTestCase.class, 3, 2, 2, 0); + } - ExecutionEventRecorder eventRecorder = executeTestsForClass(testClass); + @Test + void instancePerClassWithNestedTestClass() { + performAssertions(InstancePerClassOuterTestCase.class, 3, 1, 1, 1); + } - // eventRecorder.eventStream().forEach(System.out::println); + private void performAssertions(Class testClass, int containers, int instances, int nestedInstances, + int allMethods) { + + ExecutionEventRecorder eventRecorder = executeTestsForClass(testClass); // @formatter:off assertAll( - () -> assertEquals(2, eventRecorder.getContainerStartedCount(), "# containers started"), - () -> assertEquals(2, eventRecorder.getContainerFinishedCount(), "# containers finished"), + () -> assertEquals(containers, eventRecorder.getContainerStartedCount(), "# containers started"), + () -> assertEquals(containers, eventRecorder.getContainerFinishedCount(), "# containers finished"), () -> assertEquals(2, eventRecorder.getTestStartedCount(), "# tests started"), () -> assertEquals(2, eventRecorder.getTestSuccessfulCount(), "# tests succeeded"), - () -> assertEquals(expectedInstanceCount, instanceCount, "instance count"), - () -> assertEquals(expectedBeforeAllCount, beforeAllCount, "@BeforeAll count"), - () -> assertEquals(expectedAfterAllCount, afterAllCount, "@AfterAll count"), + () -> assertEquals(instances, instanceCount, "instance count"), + () -> assertEquals(nestedInstances, nestedInstanceCount, "nested instance count"), + () -> assertEquals(allMethods, beforeAllCount, "@BeforeAll count"), + () -> assertEquals(allMethods, afterAllCount, "@AfterAll count"), () -> assertEquals(2, beforeEachCount, "@BeforeEach count"), () -> assertEquals(2, afterEachCount, "@AfterEach count") ); // @formatter:on } + // ------------------------------------------------------------------------- + // The following is commented out b/c it's the default. // @TestInstance(Lifecycle.PER_METHOD) private static class InstancePerMethodTestCase { @@ -140,4 +149,92 @@ void afterAll(TestInfo testInfo) { } + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + private static class InstancePerMethodOuterTestCase { + + @SuppressWarnings("unused") + InstancePerMethodOuterTestCase() { + instanceCount++; + } + + @Nested + // The following is commented out b/c it's the default. + // @TestInstance(Lifecycle.PER_METHOD) + class NestedInstancePerMethodTestCase { + + @SuppressWarnings("unused") + NestedInstancePerMethodTestCase() { + nestedInstanceCount++; + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1() { + } + + @Test + void test2() { + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + } + } + + @TestInstance(Lifecycle.PER_CLASS) + private static class InstancePerClassOuterTestCase { + + @SuppressWarnings("unused") + InstancePerClassOuterTestCase() { + instanceCount++; + } + + @Nested + @TestInstance(Lifecycle.PER_CLASS) + class NestedInstancePerClassTestCase { + + @SuppressWarnings("unused") + NestedInstancePerClassTestCase() { + nestedInstanceCount++; + } + + @BeforeAll + void beforeAll(TestInfo testInfo) { + assertNotNull(testInfo); + beforeAllCount++; + } + + @BeforeEach + void beforeEach() { + beforeEachCount++; + } + + @Test + void test1() { + } + + @Test + void test2() { + } + + @AfterEach + void afterEach() { + afterEachCount++; + } + + @AfterAll + void afterAll(TestInfo testInfo) { + assertNotNull(testInfo); + afterAllCount++; + } + } + } + }