Skip to content

Commit

Permalink
Merge branch 'main' into feature/2816
Browse files Browse the repository at this point in the history
  • Loading branch information
YongGoose authored Feb 21, 2025
2 parents 6b4ee03 + c5359ec commit 7b47567
Show file tree
Hide file tree
Showing 78 changed files with 4,706 additions and 395 deletions.
3 changes: 3 additions & 0 deletions documentation/src/docs/asciidoc/link-attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ endif::[]
:ClassOrderer_OrderAnnotation: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.OrderAnnotation.html[ClassOrderer.OrderAnnotation]
:ClassOrderer_Random: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.Random.html[ClassOrderer.Random]
:ClassOrderer: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ClassOrderer.html[ClassOrderer]
:ContainerTemplate: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/ContainerTemplate.html[@ContainerTemplate]
:Disabled: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/Disabled.html[@Disabled]
:MethodOrderer_Alphanumeric: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.Alphanumeric.html[MethodOrderer.Alphanumeric]
:MethodOrderer_DisplayName: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/MethodOrderer.DisplayName.html[MethodOrderer.DisplayName]
Expand Down Expand Up @@ -142,6 +143,8 @@ endif::[]
:BeforeAllCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeAllCallback.html[BeforeAllCallback]
:BeforeEachCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeEachCallback.html[BeforeEachCallback]
:BeforeTestExecutionCallback: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/BeforeTestExecutionCallback.html[BeforeTestExecutionCallback]
:ContainerTemplateInvocationContext: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ContainerTemplateInvocationContext.html[ContainerTemplateInvocationContext]
:ContainerTemplateInvocationContextProvider: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ContainerTemplateInvocationContextProvider.html[ContainerTemplateInvocationContextProvider]
:ExecutableInvoker: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutableInvoker.html[ExecutableInvoker]
:ExecutionCondition: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExecutionCondition.html[ExecutionCondition]
:ExtendWith: {javadoc-root}/org.junit.jupiter.api/org/junit/jupiter/api/extension/ExtendWith.html[@ExtendWith]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ repository on GitHub.
[[release-notes-5.13.0-M1-junit-jupiter-bug-fixes]]
==== Bug Fixes

* ❓
* If `@ParameterizedTest(autoCloseArguments = true)`, all arguments returned by the used
`ArgumentsProvider` implementations are now closed even if the test method declares
fewer parameters.
* `AutoCloseable` arguments returned by an `ArgumentsProvider` are now closed even if they
are wrapped with `Named`.
* `AutoCloseable` arguments returned by an `ArgumentsProvider` are now closed even if a
failure happens prior to invoking the parameterized method.

[[release-notes-5.13.0-M1-junit-jupiter-deprecations-and-breaking-changes]]
==== Deprecations and Breaking Changes
Expand All @@ -45,7 +51,15 @@ repository on GitHub.
[[release-notes-5.13.0-M1-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements

* ❓
* Introduce `@ContainerTemplate` and `ContainerTemplateInvocationContextProvider` that
allow declaring a top-level or `@Nested` test class as a template to be invoked multiple
times. This may be used, for example, to inject different parameters to be used by all
tests in the container template class or to set up each invocation of the container
template differently.
* New `TestTemplateInvocationContext.prepareInvocation(ExtensionContext)` callback method
allows preparing the `ExtensionContext` before the test template method is invoked. This
may be used, for example, to store entries in its `Store` to benefit from its cleanup
support or for retrieval by other extensions.


[[release-notes-5.13.0-M1-junit-vintage]]
Expand Down
40 changes: 40 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/extensions.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,46 @@ You may override the `getTestInstantiationExtensionContextScope(...)` method to
on the test method level.
====

[[extensions-container-templates]]
=== Providing Invocation Contexts for Container Templates

A `{ContainerTemplate}` class can only be executed when at least one
`{ContainerTemplateInvocationContextProvider}` is registered. Each such provider is
responsible for providing a `Stream` of `{ContainerTemplateInvocationContext}` instances.
Each context may specify a custom display name and a list of additional extensions that
will only be used for the next invocation of the `{ContainerTemplate}` class.

The following example shows how to write a container template as well as how to register
and implement a `{ContainerTemplateInvocationContextProvider}`.

[source,java,indent=0]
.A container template with accompanying extension
----
include::{testDir}/example/ContainerTemplateDemo.java[tags=user_guide]
----

In this example, the container template will be invoked twice, meaning all test methods in
the container template class will be executed twice. The display names of the container
invocations will be `apple` and `banana` as specified by the invocation context. Each
invocation registers a custom `{TestInstancePostProcessor}` which is used to inject a
value into a field. The output when using the `ConsoleLauncher` is as follows.

....
└─ ContainerTemplateDemo ✔
├─ apple ✔
│ ├─ notNull() ✔
│ └─ wellKnown() ✔
└─ banana ✔
├─ notNull() ✔
└─ wellKnown() ✔
....

The `{ContainerTemplateInvocationContextProvider}` extension API is primarily intended for
implementing different kinds of tests that rely on repetitive invocation of _all_ test
methods in a test class albeit in different contexts — for example, with different
parameters, by preparing the test class instance differently, or multiple times without
modifying the context.

[[extensions-test-templates]]
=== Providing Invocation Contexts for Test Templates

Expand Down
17 changes: 14 additions & 3 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ in the `junit-jupiter-api` module.
| `@ParameterizedTest` | Denotes that a method is a <<writing-tests-parameterized-tests, parameterized test>>. Such methods are inherited unless they are overridden.
| `@RepeatedTest` | Denotes that a method is a test template for a <<writing-tests-repeated-tests, repeated test>>. Such methods are inherited unless they are overridden.
| `@TestFactory` | Denotes that a method is a test factory for <<writing-tests-dynamic-tests, dynamic tests>>. Such methods are inherited unless they are overridden.
| `@TestTemplate` | Denotes that a method is a <<writing-tests-test-templates, template for test cases>> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <<extensions-test-templates, providers>>. Such methods are inherited unless they are overridden.
| `@TestTemplate` | Denotes that a method is a <<writing-tests-test-templates, template for a test case>> designed to be invoked multiple times depending on the number of invocation contexts returned by the registered <<extensions-test-templates, providers>>. Such methods are inherited unless they are overridden.
| `@TestClassOrder` | Used to configure the <<writing-tests-test-execution-order-classes, test class execution order>> for `@Nested` test classes in the annotated test class. Such annotations are inherited.
| `@TestMethodOrder` | Used to configure the <<writing-tests-test-execution-order-methods, test method execution order>> for the annotated test class; similar to JUnit 4's `@FixMethodOrder`. Such annotations are inherited.
| `@TestInstance` | Used to configure the <<writing-tests-test-instance-lifecycle, test instance lifecycle>> for the annotated test class. Such annotations are inherited.
Expand All @@ -42,6 +42,7 @@ in the `junit-jupiter-api` module.
| `@AfterEach` | Denotes that the annotated method should be executed _after_ *each* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, or `@TestFactory` method in the current class; analogous to JUnit 4's `@After`. Such methods are inherited unless they are overridden.
| `@BeforeAll` | Denotes that the annotated method should be executed _before_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@BeforeClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" <<writing-tests-test-instance-lifecycle, test instance lifecycle>> is used.
| `@AfterAll` | Denotes that the annotated method should be executed _after_ *all* `@Test`, `@RepeatedTest`, `@ParameterizedTest`, and `@TestFactory` methods in the current class; analogous to JUnit 4's `@AfterClass`. Such methods are inherited unless they are overridden and must be `static` unless the "per-class" <<writing-tests-test-instance-lifecycle, test instance lifecycle>> is used.
| `@ContainerTemplate` | Denotes that the annotated class is a <<writing-tests-container-templates, template for a set of test cases>> designed to be executed multiple times depending on the number of invocation contexts returned by the registered <<extensions-container-templates, providers>>.
| `@Nested` | Denotes that the annotated class is a non-static <<writing-tests-nested,nested test class>>. On Java 8 through Java 15, `@BeforeAll` and `@AfterAll` methods cannot be used directly in a `@Nested` test class unless the "per-class" <<writing-tests-test-instance-lifecycle, test instance lifecycle>> is used. Beginning with Java 16, `@BeforeAll` and `@AfterAll` methods can be declared as `static` in a `@Nested` test class with either test instance lifecycle mode. Such annotations are not inherited.
| `@Tag` | Used to declare <<writing-tests-tagging-and-filtering,tags for filtering tests>>, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level.
| `@Disabled` | Used to <<writing-tests-disabling,disable>> a test class or test method; analogous to JUnit 4's `@Ignore`. Such annotations are not inherited.
Expand Down Expand Up @@ -2448,12 +2449,22 @@ lifecycle methods (e.g. `@BeforeEach`) and test class constructors.
include::{testDir}/example/ParameterizedTestDemo.java[tags=ParameterResolver_example]
----

[[writing-tests-container-templates]]
=== Container Templates

A `{ContainerTemplate}` class is not a regular test class but rather a template for the
contained test cases. As such, it is designed to be invoked multiple times depending on
invocation contexts returned by the registered providers. Thus, it must be used in
conjunction with a registered `{ContainerTemplateInvocationContextProvider}` extension.
Each invocation of a container template class behaves like the execution of a regular test
class with full support for the same lifecycle callbacks and extensions. Please refer to
<<extensions-container-templates>> for usage examples.

[[writing-tests-test-templates]]
=== Test Templates

A `{TestTemplate}` method is not a regular test case but rather a template for test
cases. As such, it is designed to be invoked multiple times depending on the number of
A `{TestTemplate}` method is not a regular test case but rather a template for a test
case. As such, it is designed to be invoked multiple times depending on the number of
invocation contexts returned by the registered providers. Thus, it must be used in
conjunction with a registered `{TestTemplateInvocationContextProvider}` extension. Each
invocation of a test template method behaves like the execution of a regular `@Test`
Expand Down
99 changes: 99 additions & 0 deletions documentation/src/test/java/example/ContainerTemplateDemo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package example;

import static java.util.Collections.singletonList;
import static java.util.Collections.unmodifiableList;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import example.ContainerTemplateDemo.MyContainerTemplateInvocationContextProvider;

import org.junit.jupiter.api.ContainerTemplate;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ContainerTemplateInvocationContext;
import org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.Extension;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;

// tag::user_guide[]
@ContainerTemplate
@ExtendWith(MyContainerTemplateInvocationContextProvider.class)
class ContainerTemplateDemo {

static final List<String> WELL_KNOWN_FRUITS
// tag::custom_line_break[]
= unmodifiableList(Arrays.asList("apple", "banana", "lemon"));

private String fruit;

@Test
void notNull() {
assertNotNull(fruit);
}

@Test
void wellKnown() {
assertTrue(WELL_KNOWN_FRUITS.contains(fruit));
}

// end::user_guide[]
static
// tag::user_guide[]
public class MyContainerTemplateInvocationContextProvider
// tag::custom_line_break[]
implements ContainerTemplateInvocationContextProvider {

@Override
public boolean supportsContainerTemplate(ExtensionContext context) {
return true;
}

@Override
public Stream<ContainerTemplateInvocationContext>
// tag::custom_line_break[]
provideContainerTemplateInvocationContexts(ExtensionContext context) {

return Stream.of(invocationContext("apple"), invocationContext("banana"));
}

private ContainerTemplateInvocationContext invocationContext(String parameter) {
return new ContainerTemplateInvocationContext() {
@Override
public String getDisplayName(int invocationIndex) {
return parameter;
}

// end::user_guide[]
@SuppressWarnings("Convert2Lambda")
// tag::user_guide[]
@Override
public List<Extension> getAdditionalExtensions() {
return singletonList(new TestInstancePostProcessor() {
@Override
public void postProcessTestInstance(
// tag::custom_line_break[]
Object testInstance, ExtensionContext context) {
((ContainerTemplateDemo) testInstance).fruit = parameter;
}
});
}
};
}
}
}
// end::user_guide[]
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ log4j = "2.24.3"
logback = "1.5.16"
mockito = "5.15.2"
opentest4j = "1.3.0"
openTestReporting = "0.2.0-M3"
openTestReporting = "0.2.0"
snapshotTests = "1.11.0"
surefire = "3.5.2"
xmlunit = "2.10.0"
Expand Down Expand Up @@ -94,7 +94,7 @@ asciidoctorPdf = { id = "org.asciidoctor.jvm.pdf", version.ref = "asciidoctor-pl
bnd = { id = "biz.aQute.bnd", version.ref = "bnd" }
buildParameters = { id = "org.gradlex.build-parameters", version = "1.4.4" }
commonCustomUserData = { id = "com.gradle.common-custom-user-data-gradle-plugin", version = "2.1" }
develocity = { id = "com.gradle.develocity", version = "3.19.1" }
develocity = { id = "com.gradle.develocity", version = "3.19.2" }
foojayResolver = { id = "org.gradle.toolchains.foojay-resolver", version = "0.9.0" }
gitPublish = { id = "org.ajoberstar.git-publish", version = "5.1.0" }
jmh = { id = "me.champeau.jmh", version = "0.7.3" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@
* executed <em>after</em> <strong>all</strong> tests in the current test class.
*
* <p>In contrast to {@link AfterEach @AfterEach} methods, {@code @AfterAll}
* methods are only executed once for a given test class.
* methods are only executed once per execution of a given test class. If the
* test class is annotated with {@link ContainerTemplate @ContainerTemplate},
* the {@code @AfterAll} methods are executed once after the last invocation of
* the container template. If a {@link Nested @Nested} test class is declared in
* a {@link ContainerTemplate @ContainerTemplate} class, its {@code @AfterAll}
* methods are called once per execution of the nested test class, namely, once
* per invocation of the outer container template.
*
* <h2>Method Signatures</h2>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@
* executed <em>before</em> <strong>all</strong> tests in the current test class.
*
* <p>In contrast to {@link BeforeEach @BeforeEach} methods, {@code @BeforeAll}
* methods are only executed once for a given test class.
* methods are only executed once per execution of a given test class. If the
* test class is annotated with {@link ContainerTemplate @ContainerTemplate},
* the {@code @BeforeAll} methods are executed once before the first invocation
* of the container template. If a {@link Nested @Nested} test class is declared
* in a {@link ContainerTemplate @ContainerTemplate} class, its
* {@code @BeforeAll} methods are called once per execution of the nested test
* class, namely, once per invocation of the outer container template.
*
* <h2>Method Signatures</h2>
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2015-2025 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;
import org.junit.platform.commons.annotation.Testable;

/**
* {@code @ContainerTemplate} is used to signal that the annotated class is a
* <em>container template</em>.
*
* <p>In contrast to regular test classes, a container template is not directly
* a test class but rather a template for a set of test cases. As such, it is
* designed to be invoked multiple times depending on the number of {@linkplain
* org.junit.jupiter.api.extension.ContainerTemplateInvocationContext invocation
* contexts} returned by the registered {@linkplain
* org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider
* providers}. Must be used together with at least one provider. Otherwise,
* execution will fail.
*
* <p>Each invocation of a container template method behaves like the execution
* of a regular test class with full support for the same lifecycle callbacks
* and extensions.
*
* <p>{@code @ContainerTemplate} may be combined with {@link Nested @Nested} and
* a container template may contain regular nested test classes or nested
* container templates.
*
* <p>{@code @ContainerTemplate} may also be used as a meta-annotation in order
* to create a custom <em>composed annotation</em> that inherits the semantics
* of {@code @ContainerTemplate}.
*
* <h2>Inheritance</h2>
*
* <p>The {@code @ContainerTemplate} annotation is not inherited to subclasses
* but needs to be declared on each container template class individually.
*
* @since 5.13
* @see TestTemplate
* @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContext
* @see org.junit.jupiter.api.extension.ContainerTemplateInvocationContextProvider
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = EXPERIMENTAL, since = "5.13")
@Testable
public @interface ContainerTemplate {
}
Loading

0 comments on commit 7b47567

Please sign in to comment.