JUnit 5 Extensions: From Conditional Test Execution to Test Templates

JUnit 5 Extensions: From Conditional Test Execution to Test Templates

JUnit 5 has finally arrived! It introduces a completely new extension model that allows to customize almost every aspect of test execution. For example, it lets you define custom conditions to decide whether a test should be executed or skipped. Test lifecycle callbacks allow to encapsulate common setup/teardown code in an extension. An extension can pass values or inject dependencies to a test by post-processing test instances, or resolving test method parameters. Moreover, you can even write an extension that specifies how a test method becomes a template for multiple tests and how to invoke those, e.g. multiple times with different parameters or a different setup. In this talk, we will go on an example-driven tour of the new extension API using real-world testing scenarios. We will learn about the utilities an extension can use, e.g. how it should store state, so you will be ready to write your own extensions after this session. The talk will be interesting for experienced Java developers with prior knowledge of JUnit (at least JUnit 4).

956c7d246841e8507a1e1b96842994db?s=128

Marc Philipp

May 10, 2018
Tweet

Transcript

  1. 2.

    5 MARC PHILIPP So ware Engineer at JUnit Maintainer since

    2012 Twi er: Web: @marcphilipp marcphilipp.de
  2. 3.

    5 JUNIT 5 IS HERE! 5.0 September 10, 2017 5.1

    February 18, 2018 5.2 April 29, 2018
  3. 5.

    5 J U N I T 5 P L AT

    F O R M Another Test P A R T Y T H I R D J U P I T E R V I N TA G E
  4. 8.

    5 PROGRAMMING MODEL import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; class SimpleTest

    { @Test @DisplayName("1 + 1 = 2") void onePlusOneEqualsTwo() { assertEquals(2, 1 + 1); } }
  5. 9.

    5 EXTENSION MODEL import org.junit.jupiter.api.extension.*; class MyCustomExtension implements BeforeEachCallback, AfterEachCallback

    { @Override public void beforeEach(ExtensionContext context) { // setup } @Override public void afterEach(ExtensionContext context) { // teardown } }
  6. 10.

    5 EXTENSION REGISTRATION Declara ve: @ExtendWith on classes or methods

    Programma c: @RegisterExtension on fields Global: via ServiceLoader You can register as many extensions as you need simultaneously
  7. 11.

    5 PROGRAMMING MODEL MEETS EXTENSION MODEL import org.junit.jupiter.api.*; import org.junit.jupiter.api.extension.*;

    import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MyCustomExtension.class) class SimpleTest { @RegisterExtension Extension myExtension = new AnotherExtension(42); @Test @ExtendWith({FooExtension.class, BarExtension.class}) void onePlusOneEqualsTwo() { assertEquals(2, 1 + 1); } }
  8. 14.

    5 EXTENSION CONTEXT Represents the current node in the test

    tree, e.g. test method or class Provides access to meta informa on about such a node, e.g. display name, method, class
  9. 15.

    5 EXTENSION CONTEXT root : ExtensionContext displayName = "JUnit Jupiter"

    parent class1 : ExtensionContext displayName = "TestClass1" parent test1 : ExtensionContext displayName = "test1()" parent test2 : ExtensionContext displayName = "test2()" parent class2 : ExtensionContext displayName = "TestClass2" parent test3 : ExtensionContext displayName = "test3()"
  10. 16.

    5 LESSONS LEARNED Using custom logic to determine whether a

    test class/method should be skipped Registering an extension globally Deac va ng a condi on without changing the code
  11. 19.

    5 WHY THE STORE ABSTRACTION? An extension needs to save

    and retrieve data, e.g. to clean up in the end Extensions are instan ated once and called for mul ple tests Using instance variables would be error‑prone extensionContext .getStore(Namespace.create(...)) .getOrComputeIfAbsent("key", key -> ...)
  12. 20.

    5 STORE Map ‑like interface for extensions to save and

    retrieve data, e.g. store.put(key, value) Accessed via a Namespace : Enables sharing data across extensions, but makes it a deliberate decision (e.g. Namespace.GLOBAL ) Reading from a Store follows the hierarchy upwards, if a key is not found
  13. 21.

    5 STORE root : ExtensionContext displayName = "JUnit Jupiter" parent

    class1 : ExtensionContext displayName = "TestClass1" parent store test1 : ExtensionContext displayName = "test1()" a : Store + get(Object, Class<T>): T, ... parent b : Store + get(Object, Class<T>): T, ... parent c : Store + get(Object, Class<T>): T, ... Namespace store Namespace store Namespace
  14. 22.

    5 AUTOMATIC CLEAN‑UP CloseableResource instances in a Store are automa

    cally closed when the scope of the corresponding ExtensionContext ends. class DockerClientResource implements CloseableResource { private final DockerClient dockerClient; DockerClientResource() { var config = DefaultDockerClientConfig.createDefaultConfigBuilder dockerClient = DockerClientBuilder.getInstance(config).build(); } DockerClient get() { return dockerClient; } @Override public void close() throws Throwable { dockerClient.close(); } }
  15. 23.

    5 LESSONS LEARNED Using the Store class for extension state

    Using Lifecycle Callbacks to enable reuse of common setup/teardown code Implemen ng mul ple Extension interfaces in a single extension
  16. 26.

    5 LESSONS LEARNED How to resolve test method parameters in

    an Extension ? You can also inject parameters into test class constructors and @BeforeEach / @AfterEach methods etc.
  17. 29.

    5 LESSONS LEARNED Using Parameterized Tests Wri ng a custom

    ArgumentsProvider that loads data from a JSON file, …
  18. 32.

    5 LESSONS LEARNED Execu ng a test mul ple mes

    with different contexts Implemen ng a TestInvocationContextProvider
  19. 33.

    5 SUPPORT CLASSES Package org.junit.platform.commons.support contains: AnnotationSupport to scan for

    custom annota ons, including meta‑annota ons ReflectionSupport for classpath scanning, finding methods, invoking them etc.
  20. 34.
  21. 35.

    5 EXTENSION POINTS Lifecycle: BeforeAllCallback , BeforeEachCallback ✅, BeforeTestExecutionCallback ,

    TestExecutionExceptionHandler , AfterTestExecutionCallback , AfterEachCallback ✅, AfterAllCallback Other: ExecutionCondition ✅, TestInstancePostProcessor , ParameterResolver ✅, TestTemplateInvocationContextProvider ✅
  22. 36.

    5 JUNIT JUPITER IS EXTENSIBLE A lot of extension points

    to choose from The JUnit team will add more in future releases Combining mul ple extension points in one extension is very powerful!
  23. 37.

    5 THIRD‑PARTY EXTENSIONS Spring, Mockito, Docker, Wiremock, JPA, Selenium/WebDriver, DbUnit,

    Ka a, Jersey, GreenMail, S3Mock, Citrus Framework, XWiki, … h ps:/ /github.com/junit‑team/junit5/wiki/Third‑party‑ Extensions
  24. 38.

    5 GETTING STARTED User Guide: Sample projects for Gradle, Maven,

    and Ant: Javadoc: h p:/ /junit.org/junit5/docs/current/user‑guide/ h ps:/ /github.com/junit‑team/junit5‑samples h p:/ /junit.org/junit5/docs/current/api/
  25. 39.

    5 WANTED: FEEDBACK! StackOverflow: Code & Issues: Twi er: h

    p:/ /stackoverflow.com/ques ons/tagged/junit5 h ps:/ /github.com/junit‑team/junit5/ h ps:/ /twi er.com/juni eam
  26. 42.