Skip to content

Commit

Permalink
add support for JUnit 5 TestInstance.Lifecycle.PER_CLASS
Browse files Browse the repository at this point in the history
Signed-off-by: Jonas Höf <jonas.hoef@tngtech.com>
  • Loading branch information
jonashoef committed Nov 13, 2024
1 parent 6d6154d commit c65ce32
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,15 @@
import java.lang.reflect.Method;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.InvocationInterceptor;
import org.junit.jupiter.api.extension.ReflectiveInvocationContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.tngtech.valueprovider.ValueProviderExtension.TestMethodCycleState.BEFORE_FIRST_CYCLE;
import static com.tngtech.valueprovider.ValueProviderExtension.TestMethodCycleState.CYCLE_COMLETED;
import static com.tngtech.valueprovider.ValueProviderExtension.TestMethodCycleState.CYCLE_STARTED;
import static com.tngtech.valueprovider.ValueProviderExtension.TestMethodCycleState.*;
import static java.lang.System.identityHashCode;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_METHOD;

public class ValueProviderExtension implements
BeforeAllCallback, AfterAllCallback,
Expand Down Expand Up @@ -47,14 +41,15 @@ enum TestMethodCycleState {
public void beforeAll(ExtensionContext context) {
logger.debug("{} beforeAll {}",
identityHashCode(this), getTestClassName(context));
startTestClassCycle();
startTestClassCycleIf(context, PER_METHOD);
}

@Override
public <T> T interceptTestClassConstructor(Invocation<T> invocation, ReflectiveInvocationContext<Constructor<T>> invocationContext,
ExtensionContext extensionContext) throws Throwable {
logger.debug("{} interceptTestClassConstructor {}",
identityHashCode(this), buildQualifiedTestMethodName(extensionContext));
startTestClassCycleIf(extensionContext, PER_CLASS);
ensureStaticInitializationOfTestClass(extensionContext);
startTestMethodCycle();
return invocation.proceed();
Expand Down Expand Up @@ -89,16 +84,23 @@ public void handleTestExecutionException(ExtensionContext context, Throwable thr
public void afterEach(ExtensionContext context) {
logger.debug("{} afterEach {}",
identityHashCode(this), buildQualifiedTestMethodName(context));
finishTestMethodCycle();
finishTestMethodCycleIf(context, PER_METHOD);
}

@Override
public void afterAll(ExtensionContext context) {
logger.debug("{} afterAll {}",
identityHashCode(this), getTestClassName(context));
finishTestMethodCycleIf(context, PER_CLASS);
finishTestClassCycle();
}

private void startTestClassCycleIf(ExtensionContext context, Lifecycle lifecycle) {
if (isLifecycle(context, lifecycle)) {
startTestClassCycle();
}
}

private void startTestClassCycle() {
ValueProviderFactory.startTestClassCycle();
resetTestMethodCycleState();
Expand All @@ -123,6 +125,12 @@ private void finishTestMethodCycleIfNecessary() {
}
}

private void finishTestMethodCycleIf(ExtensionContext context, Lifecycle lifecycle) {
if (isLifecycle(context, lifecycle)) {
finishTestMethodCycle();
}
}

private void finishTestMethodCycle() {
ValueProviderFactory.finishTestMethodCycle();
testMethodCycleState = CYCLE_COMLETED;
Expand Down Expand Up @@ -150,4 +158,8 @@ private static String getTestMethodName(ExtensionContext context) {
.map(Method::getName)
.orElse("<unknown>");
}

private static boolean isLifecycle(ExtensionContext context, Lifecycle lifecycle) {
return lifecycle == context.getTestInstanceLifecycle().orElse(null);
}
}
12 changes: 12 additions & 0 deletions junit5/src/test/java/com/tngtech/valueprovider/JUnit5Tests.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.tngtech.valueprovider;

import java.util.List;
import java.util.Objects;

import static com.tngtech.valueprovider.ValueProviderAsserter.reinitializeTestClassSeed;
import static com.tngtech.valueprovider.ValueProviderAsserter.setSeedProperties;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toList;

public final class JUnit5Tests {
/**
Expand All @@ -16,4 +21,11 @@ public static void ensureDefinedFactoryState() {
setSeedProperties();
reinitializeTestClassSeed();
}

public static List<ValueProvider> asListWithoutNulls(ValueProvider... valueProviders) {
return stream(valueProviders)
.filter(Objects::nonNull)
.collect(toList());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.tngtech.valueprovider;

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.junit.jupiter.api.extension.ExtendWith;

import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;

/**
* No need for different sequences of enabled/disabled test methods
* like for {@link Lifecycle#PER_CLASS},
* as test method cycle is started once in intercepted constructor,
* and only finished after last test method.
*
* @see DisabledEnabledDisabledTestMethodsTest
* @see EnabledDisabledEnabledTestMethodsTest
*/
@TestInstance(PER_CLASS)
@ExtendWith(ValueProviderExtension.class)
class LifecyclePerClassAllDisabledTestMethodsTest {
@Disabled
@Test
void a_test_disabled() {
}

@Disabled
@Test
void b_test_disabled() {
}

@Disabled
@Test
void c_test_disabled() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package com.tngtech.valueprovider;

import java.util.ArrayList;
import java.util.List;

import com.tngtech.junit.dataprovider.DataProvider;
import com.tngtech.junit.dataprovider.UseDataProvider;
import com.tngtech.junit.dataprovider.UseDataProviderExtension;
import org.junit.jupiter.api.*;
import org.junit.jupiter.api.MethodOrderer.MethodName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.tngtech.junit.dataprovider.DataProviders.$;
import static com.tngtech.junit.dataprovider.DataProviders.$$;
import static com.tngtech.valueprovider.JUnit5Tests.asListWithoutNulls;
import static com.tngtech.valueprovider.JUnit5Tests.ensureDefinedFactoryState;
import static com.tngtech.valueprovider.ValueProviderFactory.createRandomValueProvider;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;

@TestInstance(PER_CLASS)
@TestMethodOrder(MethodName.class)
@ExtendWith({ValueProviderExtension.class, UseDataProviderExtension.class})
class LifecyclePerClassDataProviderDemoTest {
private static final Logger logger = LoggerFactory.getLogger(LifecyclePerClassDataProviderDemoTest.class);
private static final ValueProvider classRandom1;
private static final ValueProvider classRandom2;

static {
logger.debug("{}: static initialization", LifecyclePerClassDataProviderDemoTest.class.getSimpleName());
ensureDefinedFactoryState();
classRandom1 = createRandomValueProvider();
classRandom2 = createRandomValueProvider();
}

private final ValueProvider instanceRandom = createRandomValueProvider();
private ValueProvider beforeAllRandom;
private ValueProvider dataProviderRandom;
private ValueProvider beforeEachRandom;
private ValueProvider methodRandom;

private final List<ValueProvider> randomsOfPreviousTestMethods = new ArrayList<>();

@BeforeAll
void beforeAll() {
beforeAllRandom = createRandomValueProvider();
}

@BeforeEach
void beforeEach() {
beforeEachRandom = createRandomValueProvider();
}

@AfterEach
void resetTestMethodRandoms() {
dataProviderRandom = null;
beforeEachRandom = null;
methodRandom = null;
}

@DataProvider
Object[][] testValues1() {
logger.debug("create DataProvider 1");
dataProviderRandom = createRandomValueProvider();
return $$(
$(dataProviderRandom.fixedDecoratedString("1")),
$(dataProviderRandom.fixedDecoratedString("2"))
);
}

@TestTemplate
@UseDataProvider("testValues1")
void a_should_ensure_reproducible_ValueProvider_creation_for_DataProvider(String testValue) {
assertThat(testValue).isNotEmpty();
methodRandom = createRandomValueProvider();
verifyReproducibleValueProviderCreation(dataProviderRandom, beforeEachRandom, methodRandom);
}

@TestTemplate
@UseDataProvider("testValues1")
void b_should_ensure_reproducible_ValueProvider_creation_for_same_DataProvider(String testValue) {
assertThat(testValue).isNotEmpty();
methodRandom = createRandomValueProvider();
// @DataProvider is invoked ONCE BEFORE FIRST test method using it
verifyReproducibleValueProviderCreation(beforeEachRandom, methodRandom);
}

@DataProvider
Object[][] testValues2() {
logger.debug("create DataProvider 2");
dataProviderRandom = createRandomValueProvider();
return $$(
$(dataProviderRandom.fixedDecoratedString("1")),
$(dataProviderRandom.fixedDecoratedString("2"))
);
}

@TestTemplate
@UseDataProvider("testValues2")
void c_should_ensure_proper_separation_of_test_class_and_test_method_cycles_for_DataProvider(String testValue) {
assertThat(testValue).isNotEmpty();
methodRandom = createRandomValueProvider();
verifyReproducibleValueProviderCreation(dataProviderRandom, beforeEachRandom, methodRandom);
}

@Test
void d_should_ensure_reproducible_ValueProvider_creation() {
methodRandom = createRandomValueProvider();
verifyReproducibleValueProviderCreation(beforeEachRandom, methodRandom);
}

@Test
void e_should_ensure_proper_separation_of_test_class_and_test_method_cycles() {
methodRandom = createRandomValueProvider();
verifyReproducibleValueProviderCreation(beforeEachRandom, methodRandom);
}

private void verifyReproducibleValueProviderCreation(ValueProvider... additionalMethodRandoms) {
List<ValueProvider> additionalMethodRandomList = asListWithoutNulls(additionalMethodRandoms);
new ValueProviderAsserter()
.addExpectedTestClassRandomValues(classRandom1, classRandom2)
.addExpectedTestMethodRandomValues(instanceRandom, beforeAllRandom)
.addExpectedTestMethodRandomValues(randomsOfPreviousTestMethods)
.addExpectedTestMethodRandomValues(additionalMethodRandomList)
.assertAllTestClassRandomValues()
.assertAllTestMethodRandomValues()
.assertAllSuffixes();
randomsOfPreviousTestMethods.addAll(additionalMethodRandomList);
}
}
Loading

0 comments on commit c65ce32

Please sign in to comment.