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. 5 JUNIT 5 EXTENSIONS MARC PHILIPP

  2. 5 MARC PHILIPP So ware Engineer at JUnit Maintainer since

    2012 Twi er: Web: @marcphilipp marcphilipp.de
  3. 5 JUNIT 5 IS HERE! 5.0 September 10, 2017 5.1

    February 18, 2018 5.2 April 29, 2018
  4. 5 ✋ SHOW OF HANDS

  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
  6. 5 JUNIT JUPITER P L AT F O R M

    J U P I T E R
  7. 5 JUNIT JUPITER API programming model for test authors extension

    model for extension authors
  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); } }
  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 } }
  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
  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); } }
  12. 5 CONDITIONAL TEST EXECUTION

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

  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
  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()"
  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
  17. 5 REUSABLE TEST SETUP & TEARDOWN

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

  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 -> ...)
  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
  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
  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(); } }
  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
  24. 5 RESOLVING TEST PARAMETERS

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

  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.
  27. 5 PROVIDING ARGUMENTS FOR PARAMETERIZED TESTS

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

  29. 5 LESSONS LEARNED Using Parameterized Tests Wri ng a custom

    ArgumentsProvider that loads data from a JSON file, …
  30. 5 FROM TESTS TO TEST TEMPLATES

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

  32. 5 LESSONS LEARNED Execu ng a test mul ple mes

    with different contexts Implemen ng a TestInvocationContextProvider
  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.
  34. 5 SUMMARY

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

    TestExecutionExceptionHandler , AfterTestExecutionCallback , AfterEachCallback ✅, AfterAllCallback Other: ExecutionCondition ✅, TestInstancePostProcessor , ParameterResolver ✅, TestTemplateInvocationContextProvider ✅
  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!
  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
  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/
  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
  40. 5 EXAMPLE CODE h ps:/ /github.com/marcphilipp/junit5‑extensions‑demo

  41. 5 QUESTIONS? or @marcphilipp @juni eam

  42. 5 THANKS!