Skip to content

Extensions for AroundAll, AroundEach and AroundTest #3221

@hiddewie

Description

@hiddewie

When writing a JUnit extension, I encountered problems when using the Junit Extension API that required me to change the applications in undesirable ways.

Code example below (heavily simplified) (Kotlin):

class Context {

  // thread local state

  fun executeWithSomeContext(action: Context -> Unit) =
    try {
       setup()
       action(context)
    } finally {
       cleanup()
    }
  
  internal fun setup() { } // < -- This had to be made non-internal for the extension
  internal fun cleanup() { } // < -- This had to be made non-internal for the extension
}

The use of this class is to ensure clients always use the executeWithSomeContext to use the context, and cannot use the setup and cleanup methods.

We have built a JUnit extension that is used automatically set up this context for JUnit tests, using an annotation. The JUnit extension reads the annotation, sets up the correct context before the test, runs the test and clears the context.

Currently, the extension looks like

class ContextExtension : BeforeEach, AfterEach {
  val context = // ... 
  override fun beforeEach(...) {
    context.setup()
  }
  override fun afterEach(...) {
    context.cleanup()
  }
}

Instead, it would be useful to be able to build the extension like

class ContextExtension : AroundEach {
  val context = // ... 
  override fun aroundEach(context: ExtensionContext, next: TestContinuation) {
    context.executeWithSomeContext(...) {
      next(context)
    }
  }
}

To support this use case of the JUnit extension calling the Context setup/clear methods, they had to be made public and accessible outside the defining package. This allows other (user) code to circumvent the executeWithSomeContext method and call the setup or cleanup methods directly.

My use case is about setting up a context and automatically clearing it after the test, but this could be extended to other types of resources can be used, but not set up, stored or manually cleared by user code.

Even a common guide to JUnit extensions, https://www.baeldung.com/junit-5-extensions#3-lifecycle-callbacks, lists a database connection with a manual cleanup that could benefit from an Around* type extension, with a try/finally block to support correct cleaning up of resources.

Other languages and test frameworks have similar around extension APIs, for example around in RSpec (https://rubydoc.info/gems/rspec-core/RSpec%2FCore%2FHooks:around).

Another benefit of the Around* extension API is a clear stacktrace showing the call stack of each extension running around the actual JUnit test.

Some semantics that these interfaces should expose:

  • The next argument must be invoked exactly once by the extension.
  • The name and type of the next argument to the aroundEach method of the AroundEach interface could be improved.

Deliverables

  • Addition of AroundAll, AroundEach, AroundTest interfaces which will be handled in the test extension lifecycle by JUnit.
  • Optionally deprecate the Before* and After*, because they could be replaced by the Around* extension lifecycles.

Thanks in advance.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions