Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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).

Marc Philipp

May 10, 2018
Tweet

More Decks by Marc Philipp

Other Decks in Programming

Transcript

  1. 5
    JUNIT 5 EXTENSIONS
    MARC PHILIPP

    View Slide

  2. 5
    MARC PHILIPP
    So ware Engineer at
    JUnit Maintainer since 2012
    Twi er:
    Web:
    @marcphilipp
    marcphilipp.de

    View Slide

  3. 5
    JUNIT 5 IS HERE!
    5.0
    September 10, 2017
    5.1
    February 18, 2018
    5.2
    April 29, 2018

    View Slide

  4. 5

    SHOW OF HANDS

    View Slide

  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

    View Slide

  6. 5
    JUNIT JUPITER
    P L AT F O R M
    J U P I T E R

    View Slide

  7. 5
    JUNIT JUPITER API
    programming model for test authors
    extension model for extension authors

    View Slide

  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);
    }
    }

    View Slide

  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
    }
    }

    View Slide

  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

    View Slide

  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);
    }
    }

    View Slide

  12. 5
    CONDITIONAL TEST
    EXECUTION

    View Slide

  13. 5
    DEMO
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  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

    View Slide

  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()"

    View Slide

  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

    View Slide

  17. 5
    REUSABLE TEST
    SETUP & TEARDOWN

    View Slide

  18. 5
    DEMO
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  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 -> ...)

    View Slide

  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

    View Slide

  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, ...
    parent
    b : Store
    + get(Object, Class): T, ...
    parent
    c : Store
    + get(Object, Class): T, ...
    Namespace
    store
    Namespace
    store
    Namespace

    View Slide

  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();
    }
    }

    View Slide

  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

    View Slide

  24. 5
    RESOLVING TEST
    PARAMETERS

    View Slide

  25. 5
    DEMO
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  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.

    View Slide

  27. 5
    PROVIDING
    ARGUMENTS FOR
    PARAMETERIZED
    TESTS

    View Slide

  28. 5
    DEMO
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  29. 5
    LESSONS LEARNED
    Using Parameterized Tests
    Wri ng a custom ArgumentsProvider that loads data
    from a JSON file, …

    View Slide

  30. 5
    FROM TESTS TO TEST
    TEMPLATES

    View Slide

  31. 5
    DEMO
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  32. 5
    LESSONS LEARNED
    Execu ng a test mul ple mes with different contexts
    Implemen ng a TestInvocationContextProvider

    View Slide

  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.

    View Slide

  34. 5
    SUMMARY

    View Slide

  35. 5
    EXTENSION POINTS
    Lifecycle: BeforeAllCallback , BeforeEachCallback ✅,
    BeforeTestExecutionCallback ,
    TestExecutionExceptionHandler ,
    AfterTestExecutionCallback , AfterEachCallback ✅,
    AfterAllCallback
    Other: ExecutionCondition ✅,
    TestInstancePostProcessor , ParameterResolver ✅,
    TestTemplateInvocationContextProvider ✅

    View Slide

  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!

    View Slide

  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

    View Slide

  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/

    View Slide

  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

    View Slide

  40. 5
    EXAMPLE CODE
    h ps:/
    /github.com/marcphilipp/junit5‑extensions‑demo

    View Slide

  41. 5
    QUESTIONS?
    or
    @marcphilipp @juni eam

    View Slide

  42. 5
    THANKS!

    View Slide