Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into issues/157-invocati…
Browse files Browse the repository at this point in the history
…on-interceptor
  • Loading branch information
marcphilipp committed Apr 13, 2019
2 parents fcd8058 + 30cd154 commit 3fc8eb5
Show file tree
Hide file tree
Showing 11 changed files with 267 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ on GitHub.
the log in order to allow reproducible builds.
* Methods ordered with `MethodOrderer.Random` now execute using the `SAME_THREAD`
concurrency mode instead of the `CONCURRENT` mode when no custom seed is provided.
* New `emptyValue` attribute in `@CsvFileSource` and `@CsvSource`.
* New `InvocationInterceptor` extension API (see
<<../user-guide/index.adoc#extensions-intercepting-invocations, User Guide>> for
details)
Expand Down
14 changes: 7 additions & 7 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1134,9 +1134,9 @@ include::{testDir}/example/ParameterizedTestDemo.java[tags=CsvSource_example]

`@CsvSource` uses a single quote `'` as its quote character. See the `'lemon, lime'` value
in the example above and in the table below. An empty, quoted value `''` results in an
empty `String`; whereas, an entirely _empty_ value is interpreted as a `null` reference.
An `ArgumentConversionException` is raised if the target type of a `null` reference is a
primitive type.
empty `String` unless the `emptyValue` attribute is set; whereas, an entirely _empty_
value is interpreted as a `null` reference. An `ArgumentConversionException` is raised if
the target type of a `null` reference is a primitive type.

[cols="50,50"]
|===
Expand Down Expand Up @@ -1167,10 +1167,10 @@ include::{testResourcesDir}/two-column.csv[]

NOTE: In contrast to the syntax used in `@CsvSource`, `@CsvFileSource` uses a double
quote `"` as the quote character. See the `"United States of America"` value in the
example above. An empty, quoted value `""` results in an empty `String`; whereas, an
entirely _empty_ value is interpreted as a `null` reference. An
`ArgumentConversionException` is raised if the target type of a `null` reference is a
primitive type.
example above. An empty, quoted value `""` results in an empty `String` unless the
`emptyValue` attribute is set; whereas, an entirely _empty_ value is interpreted as a
`null` reference. An `ArgumentConversionException` is raised if the target type of a
`null` reference is a primitive type.

[[writing-tests-parameterized-tests-sources-ArgumentsSource]]
===== @ArgumentsSource
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,66 +19,73 @@
import org.junit.platform.commons.util.Preconditions;

/**
* {@code DisplayNameGenerator} defines the SPI for generating display
* names programmatically.
* {@code DisplayNameGenerator} defines the SPI for generating display names
* programmatically.
*
* <p>An implementation must provide an accessible no-arg constructor.
* <p>Display names are typically used for test reporting in IDEs and build
* tools and may contain spaces, special characters, and even emoji.
*
* <p>Concrete implementations must have a <em>default constructor</em>.
*
* @since 5.4
* @see DisplayName
* @see DisplayNameGeneration
* @see DisplayName @DisplayName
* @see DisplayNameGeneration @DisplayNameGeneration
*/
@API(status = EXPERIMENTAL, since = "5.4")
public interface DisplayNameGenerator {

/**
* Generate a display name for the given top-level or {@code static} nested test class.
*
* @param testClass the class generate a name for; never {@code null}
* @return the display name of the container; never {@code null} or blank
* @param testClass the class to generate a name for; never {@code null}
* @return the display name for the class; never {@code null} or blank
*/
String generateDisplayNameForClass(Class<?> testClass);

/**
* Generate a display name for the given {@link Nested @Nested} inner test class.
*
* @param nestedClass the class generate a name for; never {@code null}
* @return the display name of the container; never {@code null} or blank
* @param nestedClass the class to generate a name for; never {@code null}
* @return the display name for the nested class; never {@code null} or blank
*/
String generateDisplayNameForNestedClass(Class<?> nestedClass);

/**
* Generate a display name for the given method.
*
* @implNote The class instance passed as {@code testClass} may differ from
* the returned class by {@code testMethod.getDeclaringClass()}: e.g., when
* a test method is inherited from a super class.
* @implNote The class instance supplied as {@code testClass} may differ from
* the class returned by {@code testMethod.getDeclaringClass()} &mdash; for
* example, when a test method is inherited from a superclass.
*
* @param testClass the class the test method is invoked on; never {@code null}
* @param testMethod method to generate a display name for; never {@code null}
* @return the display name of the test; never {@code null} or blank
* @return the display name for the test; never {@code null} or blank
*/
String generateDisplayNameForMethod(Class<?> testClass, Method testMethod);

/**
* Compile a string representation from all simple parameter type names.
* Generate a string representation of the formal parameters of the supplied
* method, consisting of the {@linkplain Class#getSimpleName() simple names}
* of the parameter types, separated by commas, and enclosed in parentheses.
*
* @param method the method providing parameter types for the result; never {@code null}
* @return a string representation of all parameter types of the
* supplied method or {@code "()"} if the method has no parameters
* @param method the method from to extract the parameter types from; never
* {@code null}
* @return a string representation of all parameter types of the supplied
* method or {@code "()"} if the method declares no parameters
*/
static String parameterTypesAsString(Method method) {
Preconditions.notNull(method, "Method must not be null");
return '(' + ClassUtils.nullSafeToString(Class::getSimpleName, method.getParameterTypes()) + ')';
}

/**
* Standard display name generator.
* Standard {@code DisplayNameGenerator}.
*
* <p>The implementation matches the published behaviour when Jupiter 5.0.0
* was released.
* <p>This implementation matches the standard display name generation
* behavior in place since JUnit Jupiter 5.0 was released.
*/
class Standard implements DisplayNameGenerator {

@Override
public String generateDisplayNameForClass(Class<?> testClass) {
String name = testClass.getName();
Expand All @@ -98,10 +105,11 @@ public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod
}

/**
* Replace all underscore characters with spaces.
* {@code DisplayNameGenerator} that replaces underscores with spaces.
*
* <p>The {@code ReplaceUnderscores} generator replaces all underscore characters
* ({@code '_'}) found in class and method names with space characters: {@code ' '}.
* <p>This generator extends the functionality of {@link Standard} by
* replacing all underscores ({@code '_'}) found in class and method names
* with spaces ({@code ' '}).
*/
class ReplaceUnderscores extends Standard {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
* via {@code @ExtendWith}. Such <em>static</em> extensions are not limited in
* which extension APIs they can implement. Extensions registered via static
* fields may therefore implement class-level and instance-level extension APIs
* such as {@link BeforeAllCallback}, {@link AfterAllCallback}, and
* {@link TestInstancePostProcessor} as well as method-level extension APIs
* such as {@link BeforeEachCallback}, etc.
* such as {@link BeforeAllCallback}, {@link AfterAllCallback},
* {@link TestInstanceFactory}, and {@link TestInstancePostProcessor} as well as
* method-level extension APIs such as {@link BeforeEachCallback}, etc.
*
* <h3>Instance Fields</h3>
*
Expand All @@ -53,15 +53,34 @@
* test instance (potentially injecting the instance of the extension to be
* used into the annotated field). Thus, if such an <em>instance</em> extension
* implements class-level or instance-level extension APIs such as
* {@link BeforeAllCallback}, {@link AfterAllCallback}, or
* {@link TestInstancePostProcessor} those APIs will not be honored. By default,
* an instance extension will be registered <em>after</em> extensions that are
* registered at the method level via {@code @ExtendWith}; however, if the test
* class is configured with
* {@link BeforeAllCallback}, {@link AfterAllCallback},
* {@link TestInstanceFactory}, or {@link TestInstancePostProcessor} those APIs
* will not be honored. By default, an instance extension will be registered
* <em>after</em> extensions that are registered at the method level via
* {@code @ExtendWith}; however, if the test class is configured with
* {@link org.junit.jupiter.api.TestInstance.Lifecycle @TestInstance(Lifecycle.PER_CLASS)}
* semantics, an instance extension will be registered <em>before</em> extensions
* that are registered at the method level via {@code @ExtendWith}.
*
* <h3>Inheritance</h3>
*
* <p>{@code @RegisterExtension} fields are inherited from superclasses as long
* as they are not <em>hidden</em> or <em>overridden</em>. Furthermore,
* {@code @RegisterExtension} fields from superclasses will be registered before
* {@code @RegisterExtension} fields in subclasses.
*
* <h3>Registration Order</h3>
*
* <p>By default, if multiple extensions are registered via
* {@code @RegisterExtension}, they will be ordered using an algorithm that is
* deterministic but intentionally nonobvious. This ensures that subsequent runs
* of a test suite execute extensions in the same order, thereby allowing for
* repeatable builds. However, there are times when extensions need to be
* registered in an explicit order. To achieve that, annotate each
* {@code @RegisterExtension} field with {@link org.junit.jupiter.api.Order @Order}.
* Any {@code @RegisterExtension} field not annotated with {@code @Order} will
* appear at the end of the sorted list.
*
* <h3>Example Usage</h3>
*
* <p>In the following example, the {@code docs} field in the test class is
Expand All @@ -88,18 +107,6 @@
* }
* }</pre>
*
* <h3>Registration Order</h3>
*
* <p>By default, if multiple extensions are registered via
* {@code @RegisterExtension}, they will be ordered using an algorithm that is
* deterministic but intentionally nonobvious. This ensures that subsequent runs
* of a test suite execute extensions in the same order, thereby allowing for
* repeatable builds. However, there are times when extensions need to be
* registered in an explicit order. To achieve that, annotate each
* {@code @RegisterExtension} field with {@link org.junit.jupiter.api.Order @Order}.
* Any {@code @RegisterExtension} field not annotated with {@code @Order} will
* appear at the end of the sorted list.
*
* <h3>Supported Extension APIs</h3>
*
* <ul>
Expand All @@ -110,10 +117,12 @@
* <li>{@link AfterEachCallback}</li>
* <li>{@link BeforeTestExecutionCallback}</li>
* <li>{@link AfterTestExecutionCallback}</li>
* <li>{@link TestInstanceFactory}</li>
* <li>{@link TestInstancePostProcessor}</li>
* <li>{@link ParameterResolver}</li>
* <li>{@link TestExecutionExceptionHandler}</li>
* <li>{@link TestTemplateInvocationContextProvider}</li>
* <li>{@link TestWatcher}</li>
* </ul>
*
* @since 5.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.junit.jupiter.engine.extension;

import static org.assertj.core.api.Assertions.allOf;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
Expand All @@ -20,6 +21,8 @@
import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

import org.assertj.core.api.Condition;
Expand All @@ -29,6 +32,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
Expand All @@ -53,6 +57,8 @@
*/
class ProgrammaticExtensionRegistrationTests extends AbstractJupiterTestEngineTests {

private static final List<String> callSequence = new ArrayList<>();

@Test
void instanceLevel() {
assertOneTestSucceeded(InstanceLevelExtensionRegistrationTestCase.class);
Expand Down Expand Up @@ -83,6 +89,50 @@ void classLevelFromInterface() {
assertOneTestSucceeded(ExtensionRegistrationFromInterfaceTestCase.class);
}

@Test
void instanceLevelWithInheritedAndHiddenExtensions() {
callSequence.clear();
Class<?> testClass = InstanceLevelExtensionRegistrationParentTestCase.class;
String parent = testClass.getSimpleName();
assertOneTestSucceeded(testClass);
assertThat(callSequence).containsExactly( //
parent + " :: extension1: before test", //
parent + " :: extension2: before test" //
);

callSequence.clear();
testClass = InstanceLevelExtensionRegistrationChildTestCase.class;
String child = testClass.getSimpleName();
assertOneTestSucceeded(testClass);
assertThat(callSequence).containsExactly( //
parent + " :: extension1: before test", //
child + " :: extension2: before test", //
child + " :: extension3: before test" //
);
}

@Test
void classLevelWithInheritedAndHiddenExtensions() {
callSequence.clear();
Class<?> testClass = ClassLevelExtensionRegistrationParentTestCase.class;
String parent = testClass.getSimpleName();
assertOneTestSucceeded(testClass);
assertThat(callSequence).containsExactly( //
parent + " :: extension1: before test", //
parent + " :: extension2: before test" //
);

callSequence.clear();
testClass = ClassLevelExtensionRegistrationChildTestCase.class;
String child = testClass.getSimpleName();
assertOneTestSucceeded(testClass);
assertThat(callSequence).containsExactly( //
parent + " :: extension1: before test", //
child + " :: extension2: before test", //
child + " :: extension3: before test" //
);
}

@Test
void instanceLevelWithNullField() {
Class<?> testClass = InstanceLevelExtensionRegistrationWithNullFieldTestCase.class;
Expand Down Expand Up @@ -344,6 +394,73 @@ public Object resolveParameter(ParameterContext parameterContext, ExtensionConte

}

static class ClassLevelExtensionRegistrationParentTestCase {

@RegisterExtension
static BeforeEachCallback extension1 = context -> callSequence.add(
ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before "
+ context.getRequiredTestMethod().getName());

@RegisterExtension
static BeforeEachCallback extension2 = context -> callSequence.add(
ClassLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before "
+ context.getRequiredTestMethod().getName());

@Test
void test() {
}

}

static class ClassLevelExtensionRegistrationChildTestCase extends ClassLevelExtensionRegistrationParentTestCase {

// "Hides" ClassLevelExtensionRegistrationParentTestCase.extension2
@RegisterExtension
static BeforeEachCallback extension2 = context -> callSequence.add(
ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before "
+ context.getRequiredTestMethod().getName());

@RegisterExtension
static BeforeEachCallback extension3 = context -> callSequence.add(
ClassLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before "
+ context.getRequiredTestMethod().getName());

}

static class InstanceLevelExtensionRegistrationParentTestCase {

@RegisterExtension
BeforeEachCallback extension1 = context -> callSequence.add(
InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension1: before "
+ context.getRequiredTestMethod().getName());

@RegisterExtension
BeforeEachCallback extension2 = context -> callSequence.add(
InstanceLevelExtensionRegistrationParentTestCase.class.getSimpleName() + " :: extension2: before "
+ context.getRequiredTestMethod().getName());

@Test
void test() {
}

}

static class InstanceLevelExtensionRegistrationChildTestCase
extends InstanceLevelExtensionRegistrationParentTestCase {

// "Hides" InstanceLevelExtensionRegistrationParentTestCase.extension2
@RegisterExtension
BeforeEachCallback extension2 = context -> callSequence.add(
InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension2: before "
+ context.getRequiredTestMethod().getName());

@RegisterExtension
BeforeEachCallback extension3 = context -> callSequence.add(
InstanceLevelExtensionRegistrationChildTestCase.class.getSimpleName() + " :: extension3: before "
+ context.getRequiredTestMethod().getName());

}

static class AbstractTestCase {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
settings.getFormat().setLineSeparator(LINE_SEPARATOR);
settings.getFormat().setQuote('\'');
settings.getFormat().setQuoteEscape('\'');
settings.setEmptyValue("");
settings.setEmptyValue(this.annotation.emptyValue());
settings.setAutoConfigurationEnabled(false);
CsvParser csvParser = new CsvParser(settings);
AtomicLong index = new AtomicLong(0);
Expand Down
Loading

0 comments on commit 3fc8eb5

Please sign in to comment.