Skip to content

Commit 551cf90

Browse files
committed
Reintroduce support for @testinstance lifecycle
Issue: #419
1 parent a7ed0bb commit 551cf90

File tree

4 files changed

+225
-8
lines changed

4 files changed

+225
-8
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2015-2017 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v1.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v10.html
9+
*/
10+
11+
package org.junit.jupiter.api;
12+
13+
import java.lang.annotation.Documented;
14+
import java.lang.annotation.ElementType;
15+
import java.lang.annotation.Retention;
16+
import java.lang.annotation.RetentionPolicy;
17+
import java.lang.annotation.Target;
18+
19+
/**
20+
* {@code @TestInstance} is a class-level annotation that is used to configure
21+
* the {@linkplain Lifecycle lifecycle} of test instances for the annotated
22+
* test class.
23+
*
24+
* <p>If {@code @TestInstance} is not declared on a test class, the lifecycle
25+
* mode will default to {@link Lifecycle#PER_METHOD PER_METHOD}.
26+
*
27+
* @author Sam Brannen
28+
* @since 5.0
29+
*/
30+
@Target(ElementType.TYPE)
31+
@Retention(RetentionPolicy.RUNTIME)
32+
@Documented
33+
public @interface TestInstance {
34+
35+
/**
36+
* Enumeration of test instance lifecycle <em>modes</em>.
37+
*/
38+
enum Lifecycle {
39+
40+
/**
41+
* When using this mode, a new test instance will be created once per test class.
42+
*
43+
* @see #PER_METHOD
44+
*/
45+
PER_CLASS,
46+
47+
/**
48+
* When using this mode, a new test instance will be created for each test method
49+
* or test factory method.
50+
*
51+
* <p>This mode is analogous to the behavior found in JUnit versions 1 through 4.
52+
*
53+
* @see #PER_CLASS
54+
*/
55+
PER_METHOD;
56+
57+
}
58+
59+
/**
60+
* The test instance lifecycle <em>mode</em> to use.
61+
*/
62+
Lifecycle value();
63+
64+
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ClassTestDescriptor.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@
2121
import java.util.ArrayList;
2222
import java.util.Collections;
2323
import java.util.List;
24+
import java.util.Optional;
2425
import java.util.Set;
2526
import java.util.function.Function;
2627

28+
import org.junit.jupiter.api.TestInstance;
29+
import org.junit.jupiter.api.TestInstance.Lifecycle;
2730
import org.junit.jupiter.api.extension.AfterAllCallback;
2831
import org.junit.jupiter.api.extension.BeforeAllCallback;
2932
import org.junit.jupiter.api.extension.ContainerExtensionContext;
@@ -40,6 +43,7 @@
4043
import org.junit.jupiter.engine.extension.ExtensionRegistry;
4144
import org.junit.platform.commons.JUnitException;
4245
import org.junit.platform.commons.meta.API;
46+
import org.junit.platform.commons.util.AnnotationUtils;
4347
import org.junit.platform.commons.util.Preconditions;
4448
import org.junit.platform.commons.util.ReflectionUtils;
4549
import org.junit.platform.engine.TestDescriptor;
@@ -172,13 +176,8 @@ public void after(JupiterEngineExecutionContext context) throws Exception {
172176

173177
protected TestInstanceProvider testInstanceProvider(JupiterEngineExecutionContext parentExecutionContext,
174178
ExtensionRegistry registry, ExtensionContext extensionContext) {
175-
return childExtensionRegistry -> {
176-
Constructor<?> constructor = ReflectionUtils.getDeclaredConstructor(this.testClass);
177-
Object instance = executableInvoker.invoke(constructor, extensionContext,
178-
childExtensionRegistry.orElse(registry));
179-
invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(registry), extensionContext);
180-
return instance;
181-
};
179+
180+
return new LifecycleAwareTestInstanceProvider(this.testClass, registry, extensionContext);
182181
}
183182

184183
protected void invokeTestInstancePostProcessors(Object instance, ExtensionRegistry registry,
@@ -272,4 +271,53 @@ private void invokeMethodInTestExtensionContext(Method method, TestExtensionCont
272271
executableInvoker.invoke(method, instance, context, registry);
273272
}
274273

274+
private final class LifecycleAwareTestInstanceProvider implements TestInstanceProvider {
275+
276+
private final Class<?> testClass;
277+
private final Lifecycle lifecycle;
278+
private final ExtensionRegistry registry;
279+
private final ExtensionContext extensionContext;
280+
private Object testInstance;
281+
282+
LifecycleAwareTestInstanceProvider(Class<?> testClass, ExtensionRegistry registry,
283+
ExtensionContext extensionContext) {
284+
285+
this.testClass = testClass;
286+
this.lifecycle = getInstanceLifecycle(testClass);
287+
this.registry = registry;
288+
this.extensionContext = extensionContext;
289+
}
290+
291+
@Override
292+
public Object getTestInstance(Optional<ExtensionRegistry> childExtensionRegistry) throws Exception {
293+
if (this.lifecycle == Lifecycle.PER_METHOD) {
294+
return createTestInstance(childExtensionRegistry);
295+
}
296+
297+
// else Lifecycle.PER_CLASS
298+
if (this.testInstance == null) {
299+
this.testInstance = createTestInstance(childExtensionRegistry);
300+
}
301+
return this.testInstance;
302+
}
303+
304+
private Object createTestInstance(Optional<ExtensionRegistry> childExtensionRegistry) {
305+
Constructor<?> constructor = ReflectionUtils.getDeclaredConstructor(this.testClass);
306+
Object instance = executableInvoker.invoke(constructor, this.extensionContext,
307+
childExtensionRegistry.orElse(this.registry));
308+
invokeTestInstancePostProcessors(instance, childExtensionRegistry.orElse(this.registry),
309+
this.extensionContext);
310+
return instance;
311+
}
312+
313+
private TestInstance.Lifecycle getInstanceLifecycle(Class<?> testClass) {
314+
// @formatter:off
315+
return AnnotationUtils.findAnnotation(testClass, TestInstance.class)
316+
.map(TestInstance::value)
317+
.orElse(Lifecycle.PER_METHOD);
318+
// @formatter:on
319+
}
320+
321+
}
322+
275323
}

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/NestedClassTestDescriptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,9 @@ public final Set<TestTag> getTags() {
6060
@Override
6161
protected TestInstanceProvider testInstanceProvider(JupiterEngineExecutionContext parentExecutionContext,
6262
ExtensionRegistry registry, ExtensionContext extensionContext) {
63+
6364
return childExtensionRegistry -> {
64-
// Extensions registered for nested classes and below are not to be used for instancing outer classes
65+
// Extensions registered for nested classes and below are not to be used for instantiating outer classes
6566
Optional<ExtensionRegistry> childExtensionRegistryForOuterInstance = Optional.empty();
6667
Object outerInstance = parentExecutionContext.getTestInstanceProvider().getTestInstance(
6768
childExtensionRegistryForOuterInstance);
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2015-2017 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v1.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* http://www.eclipse.org/legal/epl-v10.html
9+
*/
10+
11+
package org.junit.jupiter.engine;
12+
13+
import static org.junit.jupiter.api.Assertions.assertAll;
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
import org.junit.jupiter.api.AfterEach;
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.Test;
19+
import org.junit.jupiter.api.TestInstance;
20+
import org.junit.jupiter.api.TestInstance.Lifecycle;
21+
import org.junit.platform.engine.test.event.ExecutionEventRecorder;
22+
import org.junit.platform.runner.JUnitPlatform;
23+
import org.junit.runner.RunWith;
24+
25+
/**
26+
* Tests for {@link TestInstance @TestInstance} lifecycle support.
27+
*
28+
* @since 5.0
29+
*/
30+
@RunWith(JUnitPlatform.class)
31+
public
32+
33+
class TestInstanceLifecycleTests extends AbstractJupiterTestEngineTests {
34+
35+
private static int instanceCount;
36+
private static int beforeCount;
37+
private static int afterCount;
38+
39+
@BeforeEach
40+
void init() {
41+
instanceCount = 0;
42+
beforeCount = 0;
43+
afterCount = 0;
44+
}
45+
46+
@Test
47+
void instancePerMethod() {
48+
performAssertions(InstancePerMethodTestCase.class, 2);
49+
}
50+
51+
@Test
52+
void instancePerClass() {
53+
performAssertions(InstancePerClassTestCase.class, 1);
54+
}
55+
56+
private void performAssertions(Class<?> testClass, int expectedInstanceCount) {
57+
ExecutionEventRecorder eventRecorder = executeTestsForClass(testClass);
58+
59+
// @formatter:off
60+
assertAll(
61+
() -> assertEquals(expectedInstanceCount, instanceCount, "instance count"),
62+
() -> assertEquals(2, eventRecorder.getContainerStartedCount(), "# containers started"),
63+
() -> assertEquals(2, eventRecorder.getContainerFinishedCount(), "# containers finished"),
64+
() -> assertEquals(2, eventRecorder.getTestStartedCount(), "# tests started"),
65+
() -> assertEquals(2, eventRecorder.getTestSuccessfulCount(), "# tests succeeded"),
66+
() -> assertEquals(2, beforeCount, "# before calls"),
67+
() -> assertEquals(2, afterCount, "# after calls")
68+
);
69+
// @formatter:on
70+
}
71+
72+
// The following is commented out b/c it's the default.
73+
// @TestInstance(Lifecycle.PER_METHOD)
74+
private static class InstancePerMethodTestCase {
75+
76+
InstancePerMethodTestCase() {
77+
instanceCount++;
78+
}
79+
80+
@BeforeEach
81+
void before() {
82+
beforeCount++;
83+
}
84+
85+
@AfterEach
86+
void after() {
87+
afterCount++;
88+
}
89+
90+
@Test
91+
void test1() {
92+
}
93+
94+
@Test
95+
void test2() {
96+
}
97+
98+
}
99+
100+
@TestInstance(Lifecycle.PER_CLASS)
101+
private static class InstancePerClassTestCase extends InstancePerMethodTestCase {
102+
}
103+
104+
}

0 commit comments

Comments
 (0)