Registering Extensions

There is a newer version available.
For the latest stable release, please see JUnit 6.1.3!

Extensions can be registered declaratively via @ExtendWith, programmatically via @RegisterExtension, or automatically via Java’s ServiceLoader mechanism.

Declarative Extension Registration

Developers can register one or more extensions declaratively by annotating a test interface, test class, test method, or custom composed annotation with @ExtendWith(…​) and supplying class references for the extensions to register. As of JUnit Jupiter 5.8, @ExtendWith may also be declared on fields or on parameters in test class constructors, in test methods, and in @BeforeAll, @AfterAll, @BeforeEach, and @AfterEach lifecycle methods.

For example, to register a WebServerExtension for a particular test method, you would annotate the test method as follows. We assume the WebServerExtension starts a local web server and injects the server’s URL into parameters annotated with @WebServerUrl.

@Test
@ExtendWith(WebServerExtension.class)
void getProductList(@WebServerUrl String serverUrl) {
	WebClient webClient = new WebClient();
	// Use WebClient to connect to web server using serverUrl and verify response
	assertEquals(200, webClient.get(serverUrl + "/products").getResponseStatus());
}

To register the WebServerExtension for all tests in a particular class and its subclasses, you would annotate the test class as follows.

@ExtendWith(WebServerExtension.class)
class MyTests {
	// ...
}

Multiple extensions can be registered together like this:

@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
class MyFirstTests {
	// ...
}

As an alternative, multiple extensions can be registered separately like this:

@ExtendWith(DatabaseExtension.class)
@ExtendWith(WebServerExtension.class)
class MySecondTests {
	// ...
}
Extension Registration Order

Extensions registered declaratively via @ExtendWith at the class level, method level, or parameter level will be executed in the order in which they are declared in the source code. For example, the execution of tests in both MyFirstTests and MySecondTests will be extended by the DatabaseExtension and WebServerExtension, in exactly that order.

If you wish to combine multiple extensions in a reusable way, you can define a custom composed annotation and use @ExtendWith as a meta-annotation as in the following code listing. Then @DatabaseAndWebServerExtension can be used in place of @ExtendWith({ DatabaseExtension.class, WebServerExtension.class }).

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({ DatabaseExtension.class, WebServerExtension.class })
public @interface DatabaseAndWebServerExtension {
}

The above examples demonstrate how @ExtendWith can be applied at the class level or at the method level; however, for certain use cases it makes sense for an extension to be registered declaratively at the field or parameter level. Consider a RandomNumberExtension which generates random numbers that can be injected into a field or via a parameter in a constructor, test method, or lifecycle method. If the extension provides a @Random annotation that is meta-annotated with @ExtendWith(RandomNumberExtension.class) (see listing below), the extension can be used transparently as in the following RandomNumberDemo example.

@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(RandomNumberExtension.class)
public @interface Random {
}
class RandomNumberDemo {

	// Use static randomNumber0 field anywhere in the test class,
	// including @BeforeAll or @AfterEach lifecycle methods.
	@Random
	private static Integer randomNumber0;

	// Use randomNumber1 field in test methods and @BeforeEach
	// or @AfterEach lifecycle methods.
	@Random
	private int randomNumber1;

	RandomNumberDemo(@Random int randomNumber2) {
		// Use randomNumber2 in constructor.
	}

	@BeforeEach
	void beforeEach(@Random int randomNumber3) {
		// Use randomNumber3 in @BeforeEach method.
	}

	@Test
	void test(@Random int randomNumber4) {
		// Use randomNumber4 in test method.
	}

}

The following code listing provides an example of how one might choose to implement such a RandomNumberExtension. This implementation works for the use cases in RandomNumberDemo; however, it may not prove robust enough to cover all use cases — for example, the random number generation support is limited to integers; it uses java.util.Random instead of java.security.SecureRandom; etc. In any case, it is important to note which extension APIs are implemented and for what reasons.

Specifically, RandomNumberExtension implements the following extension APIs:

  • BeforeAllCallback: to support static field injection

  • TestInstancePostProcessor: to support non-static field injection

  • ParameterResolver: to support constructor and method injection

import static org.junit.platform.commons.support.AnnotationSupport.findAnnotatedFields;

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

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolver;
import org.junit.jupiter.api.extension.TestInstancePostProcessor;
import org.junit.platform.commons.support.ModifierSupport;

class RandomNumberExtension
		implements BeforeAllCallback, TestInstancePostProcessor, ParameterResolver {

	private final java.util.Random random = new java.util.Random(System.nanoTime());

	/**
	 * Inject a random integer into static fields that are annotated with
	 * {@code @Random} and can be assigned an integer value.
	 */
	@Override
	public void beforeAll(ExtensionContext context) {
		Class<?> testClass = context.getRequiredTestClass();
		injectFields(testClass, null, ModifierSupport::isStatic);
	}

	/**
	 * Inject a random integer into non-static fields that are annotated with
	 * {@code @Random} and can be assigned an integer value.
	 */
	@Override
	public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
		Class<?> testClass = context.getRequiredTestClass();
		injectFields(testClass, testInstance, ModifierSupport::isNotStatic);
	}

	/**
	 * Determine if the parameter is annotated with {@code @Random} and can be
	 * assigned an integer value.
	 */
	@Override
	public boolean supportsParameter(ParameterContext pc, ExtensionContext ec) {
		return pc.isAnnotated(Random.class) && isInteger(pc.getParameter().getType());
	}

	/**
	 * Resolve a random integer.
	 */
	@Override
	public Integer resolveParameter(ParameterContext pc, ExtensionContext ec) {
		return this.random.nextInt();
	}

	private void injectFields(Class<?> testClass, Object testInstance,
			Predicate<Field> predicate) {

		predicate = predicate.and(field -> isInteger(field.getType()));
		findAnnotatedFields(testClass, Random.class, predicate)
			.forEach(field -> {
				try {
					field.setAccessible(true);
					field.set(testInstance, this.random.nextInt());
				}
				catch (Exception ex) {
					throw new RuntimeException(ex);
				}
			});
	}

	private static boolean isInteger(Class<?> type) {
		return type == Integer.class || type == int.class;
	}

}
Extension Registration Order for @ExtendWith on Fields

Extensions registered declaratively via @ExtendWith on fields will be ordered relative to @RegisterExtension fields and other @ExtendWith fields using an algorithm that is deterministic but intentionally nonobvious. However, @ExtendWith fields can be ordered using the @Order annotation. See the Extension Registration Order tip for @RegisterExtension fields for details.

Extension Inheritance

Extensions registered declaratively via @ExtendWith on fields in superclasses will be inherited.

See Extension Inheritance for details.

@ExtendWith fields may be either static or non-static. The documentation on Static Fields and Instance Fields for @RegisterExtension fields also applies to @ExtendWith fields.