diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e152240..c7a90f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,8 +19,8 @@ (useful when TestInstance.Lifecycle.PER_CLASS used) (discussion #394) - Add getJunitContext() method for TestEnvironmentSetup (@EnableSetup) to be able to configure test application with full context access (a setup object could register hooks) (discussion #388) - - Add test lifecycle listeners: could be registered with TestEnvironmentSetup (listen() method) and provide - notifications for guicey extension lifecycle (app start/stop, before/after test). + - Add test lifecycle listeners: could be registered with TestEnvironmentSetup (listen() method or lambda-based on* methods) + and provide notifications for guicey extension lifecycle (app start/stop, before/after test). This is a simple alternative to writing junit extensions for an additional integrations (db, testcontainers etc.). - Add annotated fields search api in test class for setup objects (TestEnvironmentSetup): findFields(..) (to simplify writing annotation-driven extensions). diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/EnableSetup.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/EnableSetup.java index d9f0279e..c1f6f6a3 100644 --- a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/EnableSetup.java +++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/EnableSetup.java @@ -21,7 +21,7 @@ * method), otherwise it must be static. Incorrect usage will be indicated with an exception. *
* If setup object implements hook ({@link ru.vyarus.dropwizard.guice.hook.GuiceyConfigurationHook})
- * and/or listener ({@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestExecutionListener}) it would be
+ * and/or listener ({@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener}) it would be
* registered automatically (no need for manual registration). Manual registration would not create duplicate.
*
* @author Vyacheslav Rusakov
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/ListenersSupport.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/ListenersSupport.java
index 5dba7977..2706438c 100644
--- a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/ListenersSupport.java
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/ListenersSupport.java
@@ -3,6 +3,8 @@
import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import org.junit.jupiter.api.extension.ExtensionContext;
+import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext;
+import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener;
import ru.vyarus.dropwizard.guice.test.jupiter.ext.conf.track.GuiceyTestTime;
import ru.vyarus.dropwizard.guice.test.jupiter.ext.conf.track.TestExtensionsTracker;
@@ -30,27 +32,27 @@ public void addListener(final TestExecutionListener listener) {
}
public void broadcastStart(final ExtensionContext context) {
- broadcast(listener -> listener.started(context), false);
+ broadcast(listener -> listener.started(new EventContext(context)), false);
}
public void broadcastBeforeAll(final ExtensionContext context) {
- broadcast(listener -> listener.beforeAll(context), true);
+ broadcast(listener -> listener.beforeAll(new EventContext(context)), true);
}
public void broadcastBefore(final ExtensionContext context) {
- broadcast(listener -> listener.beforeEach(context), true);
+ broadcast(listener -> listener.beforeEach(new EventContext(context)), true);
}
public void broadcastAfter(final ExtensionContext context) {
- broadcast(listener -> listener.afterEach(context), true);
+ broadcast(listener -> listener.afterEach(new EventContext(context)), true);
}
public void broadcastAfterAll(final ExtensionContext context) {
- broadcast(listener -> listener.afterAll(context), true);
+ broadcast(listener -> listener.afterAll(new EventContext(context)), true);
}
public void broadcastStop(final ExtensionContext context) {
- broadcast(listener -> listener.stopped(context), false);
+ broadcast(listener -> listener.stopped(new EventContext(context)), false);
}
private void broadcast(final Consumer
* If auto close is not enough, use
- * {@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestExtension#listen(TestExecutionListener)} listener for
- * reacting on exact test phases.
+ * {@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestExtension#listen(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)} listener for reacting on exact test
+ * phases (or lambda-based listener methods: on*).
*
* The same could be achieved with an additional junit 5 extensions, but it might be harder to properly synchronize
* lifecycles (extensions order would be important). Environment support assumed to be a simpler alternative.
@@ -31,7 +32,7 @@
* (inside it) providing entire junit context or just some stored values.
*
* AUTO REGISTRATION: If setup object implements hook ({@link ru.vyarus.dropwizard.guice.hook.GuiceyConfigurationHook})
- * and/or listener ({@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestExecutionListener}) it would be
+ * and/or listener ({@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener}) it would be
* registered automatically. Manual registration would not create duplicate.
*
* @author Vyacheslav Rusakov
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/TestExecutionListener.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/TestExecutionListener.java
deleted file mode 100644
index 9a823274..00000000
--- a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/TestExecutionListener.java
+++ /dev/null
@@ -1,179 +0,0 @@
-package ru.vyarus.dropwizard.guice.test.jupiter.env;
-
-import com.google.inject.Injector;
-import io.dropwizard.testing.DropwizardTestSupport;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import ru.vyarus.dropwizard.guice.test.ClientSupport;
-import ru.vyarus.dropwizard.guice.test.jupiter.ext.GuiceyExtensionsSupport;
-
-/**
- * Test listener allows listening for the main test events. There are no beforeAll and afterAll events directly
- * because guicey extension could be created either on beforeAll or in beforeEach (depends on test configuration).
- *
- * Before/after test called before and after each test method. This could be used for custom setup/cleanup logic.
- * BeforeAll and afterAll might not be called - use with caution (depends on extension registration).
- *
- * This interface provides a simple replacement for junit extension (synchronized with guicey lifecycle).
- *
- * @author Vyacheslav Rusakov
- * @since 07.02.2025
- */
-public interface TestExecutionListener {
-
- /**
- * Called when dropwizard (or guicey) application started. It could be beforeAll or beforeEach phase
- * (if important, look {@link org.junit.jupiter.api.extension.ExtensionContext#getTestMethod()} to make sure).
- * Application could start/stop multiple times within one test class (if extension registered in non-static field).
- *
- * NOTE: At this stage, injections not yet performed inside test instance.
- *
- * This method could be used instead of beforeAll because normally extension is created under beforeAll, but
- * for extensions created under beforeEach - it would be impossible to notify about beforeAll anyway.
- *
- * @param context junit extension
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void started(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field
- * (and so application created before each method).
- * Prefer {@link #started(org.junit.jupiter.api.extension.ExtensionContext)} instead, which is always called
- * (but not always under beforeAll),
- *
- * Method could be useful if some action must be performed before each test (in case of nested tests or
- * global application when "start" would not be called for each test).
- *
- * @param context junit extension
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void beforeAll(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * Called before each test method execution. Guice injections into test instance already performed.
- * Even if an application is created in beforeEach phase, this method would be called after application creation.
- *
- * @param context junit extension
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void beforeEach(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * Called after each test method execution. Even if an application is closed on afterEach, this method would be
- * called before it.
- *
- * @param context junit extension
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void afterEach(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field
- * (and so the application is stopped after each method).
- * Prefer {@link #stopped(org.junit.jupiter.api.extension.ExtensionContext)} instead, which is always called (but
- * not always under afterAll),
- *
- * Method could be useful if some action must be performed after each test (in case of nested tests or
- * global application when "stop" would not be called for each test).
- *
- * @param context junit extension
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void afterAll(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * Called when dropwizard (or guicey) application stopped. It could be afterAll or afterEach phase
- * (if important, look {@link org.junit.jupiter.api.extension.ExtensionContext#getTestMethod()} to make sure).
- * Application could start/stop multiple times within one test class (if extension registered in non-static field).
- *
- * Note that in case of global application usage or for nested tests this method might not be called because
- * application lifecycle would be managed by the top-most test.
- *
- * This method could be used instead of afterAll because normally extension is stopped under afterAll, but
- * for extensions stopped under afterEach - it would be impossible to notify about afterAll anyway.
- *
- * @param context junit extension context
- * @see #getSupport(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getInjector(org.junit.jupiter.api.extension.ExtensionContext)
- * @see #getBean(org.junit.jupiter.api.extension.ExtensionContext, Class)
- * @see #getClient(org.junit.jupiter.api.extension.ExtensionContext)
- */
- default void stopped(final ExtensionContext context) {
- // empty default
- }
-
- /**
- * Shortcut method to avoid passing support as a parameter in each method. Normally, it is impossible that support
- * would not be found (under called lifecycle methods)
- *
- * @param context junit extension context
- * @return dropwizard support object (or guicey support)
- * @throws IllegalStateException if the support object not found (should be impossible)
- */
- default DropwizardTestSupport> getSupport(final ExtensionContext context) {
- return GuiceyExtensionsSupport.lookupSupport(context)
- .orElseThrow(() -> new IllegalStateException("Test support not found"));
- }
-
- /**
- * Shortcut method to avoid passing injector as a parameter in each method. Normally, it is impossible that
- * injector would not be found (under called lifecycle methods)
- *
- * @param context junit extension context
- * @return injector instance
- * @throws IllegalStateException if the injector object not found (should be impossible)
- */
- default Injector getInjector(final ExtensionContext context) {
- return GuiceyExtensionsSupport.lookupInjector(context)
- .orElseThrow(() -> new IllegalStateException("Injector not found"));
- }
-
- /**
- * Shortcut to get bean directly from injector.
- *
- * @param context junit extension context
- * @param type bean class
- * @param
+ * Might be called multiple times.
+ *
+ * @param listener listener called after application start (could be beforeAll (default) or beforeEach phase)
+ * @return builder instance for chained calls
+ */
+ public TestExtension onApplicationStart(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.Started, listener);
+ return this;
+ }
+
+ /**
+ * Lambda version of {@link #listen(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}
+ * for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener#beforeAll(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)}. Lambda listener version is more useful in
+ * case when setup object is declared as a lambda itself.
+ *
+ * Might be called multiple times.
+ *
+ * @param listener listener called (might not be called!) before all test methods
+ * @return builder instance for chained calls
+ */
+ public TestExtension onBeforeAll(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.BeforeAll, listener);
+ return this;
+ }
+
+ /**
+ * Lambda version of {@link #listen(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}
+ * for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener#beforeEach(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)}. Lambda listener version is more useful in
+ * case when setup object is declared as a lambda itself.
+ *
+ * Might be called multiple times.
+ *
+ * @param listener listener called before each test method
+ * @return builder instance for chained calls
+ */
+ public TestExtension onBeforeEach(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.BeforeEach, listener);
+ return this;
+ }
+
+ /**
+ * Lambda version of {@link #listen(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}
+ * for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener#afterEach(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)}. Lambda listener version is more useful in
+ * case when setup object is declared as a lambda itself.
+ *
+ * Might be called multiple times.
+ *
+ * @param listener listener called after each test method
+ * @return builder instance for chained calls
+ */
+ public TestExtension onAfterEach(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.AfterEach, listener);
+ return this;
+ }
+
+ /**
+ * Lambda version of {@link #listen(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}
+ * for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener#afterAll(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)}. Lambda listener version is more useful in
+ * case when setup object is declared as a lambda itself.
+ *
+ * Might be called multiple times.
+ *
+ * @param listener listener called (might not be called!) after all test methods
+ * @return builder instance for chained calls
+ */
+ public TestExtension onAfterAll(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.AfterAll, listener);
+ return this;
+ }
+
+ /**
+ * Lambda version of {@link #listen(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}
+ * for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener#stopped(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)}. Lambda listener version is more useful in
+ * case when setup object is declared as a lambda itself.
+ *
+ * Might be called multiple times.
+ *
+ * @param listener listener called after application stop (could be afterAll (default) or afterEach phase)
+ * @return builder instance for chained calls
+ */
+ public TestExtension onApplicationStop(final LambdaTestListener listener) {
+ registerListener(ListenerEvent.Stopped, listener);
+ return this;
+ }
+
/**
* Search for annotated fields with validation (a field type must be assignable to provided type).
*
@@ -93,4 +198,13 @@ public List
- * Called before {@link #getFieldValue(org.junit.jupiter.api.extension.ExtensionContext, AnnotatedField,
- * com.google.inject.Injector)}.
+ * Called before {@link #getFieldValue(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext,
+ * AnnotatedField)}.
*
- * @param context junit context
+ * @param context event context
* @param field annotated field
- * @param injector guice injector
*/
- protected abstract void validateBinding(ExtensionContext context,
- AnnotatedField field, Injector injector);
+ protected abstract void validateBinding(EventContext context, AnnotatedField field);
/**
* Get test field value (would be immediately injected into the test field). Called only if field was not
@@ -170,17 +168,16 @@ protected abstract void validateBinding(ExtensionContext context,
* from guice context (if guice was re-configured with module overrides).
*
* Warning: not called for manually initialized fields (because value already set)! To validate binding use
- * {@link #validateBinding(org.junit.jupiter.api.extension.ExtensionContext, AnnotatedField,
- * com.google.inject.Injector)} method instead (which is called for all fields).
+ * {@link #validateBinding(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext, AnnotatedField)}
+ * method instead (which is called for all fields).
*
* Application already completely started and test extension initialized at this moment (beforeEach test phase).
*
- * @param context junit context (just in case)
+ * @param context event context
* @param field annotated field
- * @param injector guice injector
* @return created field value
*/
- protected abstract T getFieldValue(ExtensionContext context, AnnotatedField field, Injector injector);
+ protected abstract T getFieldValue(EventContext context, AnnotatedField field);
/**
* Called when debug is enabled on guicey extension to report registered fields.
@@ -194,43 +191,43 @@ protected abstract void validateBinding(ExtensionContext context,
* - {@link #FIELD_MANUAL} field value was initialized by user, otherwise automatic
* - {@link #FIELD_INJECTED} field injection instance (test instance)
*
- * @param context junit context (just in case), IMPORTANT - this would be setup context and not current
+ * @param context event context, IMPORTANT - this would be setup context and not current
* @param fields fields to report
*/
- protected abstract void report(ExtensionContext context, List
+ * Before/after test called before and after each test method. This could be used for custom setup/cleanup logic.
+ * BeforeAll and afterAll might not be called - use with caution (depends on extension registration).
+ *
+ * This interface provides a simple replacement for junit extension (synchronized with guicey lifecycle).
+ *
+ * @author Vyacheslav Rusakov
+ * @since 07.02.2025
+ */
+public interface TestExecutionListener {
+
+ /**
+ * Called when dropwizard (or guicey) application started. It could be beforeAll or beforeEach phase
+ * (if important, look {@link org.junit.jupiter.api.extension.ExtensionContext#getTestMethod()} to make sure).
+ * Application could start/stop multiple times within one test class (if extension registered in non-static field).
+ *
+ * NOTE: At this stage, injections not yet performed inside test instance.
+ *
+ * This method could be used instead of beforeAll because normally extension is created under beforeAll, but
+ * for extensions created under beforeEach - it would be impossible to notify about beforeAll anyway.
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void started(final EventContext context) {
+ // empty default
+ }
+
+ /**
+ * IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field
+ * (and so application created before each method).
+ * Prefer {@link #started(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)} instead, which is
+ * always called (but not always under beforeAll),
+ *
+ * Method could be useful if some action must be performed before each test (in case of nested tests or
+ * global application when "start" would not be called for each test).
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void beforeAll(final EventContext context) {
+ // empty default
+ }
+
+ /**
+ * Called before each test method execution. Guice injections into test instance already performed.
+ * Even if an application is created in beforeEach phase, this method would be called after application creation.
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void beforeEach(final EventContext context) {
+ // empty default
+ }
+
+ /**
+ * Called after each test method execution. Even if an application is closed on afterEach, this method would be
+ * called before it.
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void afterEach(final EventContext context) {
+ // empty default
+ }
+
+ /**
+ * IMPORTANT: this method MIGHT NOT BE CALLED at all in case if extension is registered under non-static field
+ * (and so the application is stopped after each method).
+ * Prefer {@link #stopped(ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext)} instead, which is
+ * always called (but not always under afterAll),
+ *
+ * Method could be useful if some action must be performed after each test (in case of nested tests or
+ * global application when "stop" would not be called for each test).
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void afterAll(final EventContext context) {
+ // empty default
+ }
+
+ /**
+ * Called when dropwizard (or guicey) application stopped. It could be afterAll or afterEach phase
+ * (if important, look {@link org.junit.jupiter.api.extension.ExtensionContext#getTestMethod()} to make sure).
+ * Application could start/stop multiple times within one test class (if extension registered in non-static field).
+ *
+ * Note that in case of global application usage or for nested tests this method might not be called because
+ * application lifecycle would be managed by the top-most test.
+ *
+ * This method could be used instead of afterAll because normally extension is stopped under afterAll, but
+ * for extensions stopped under afterEach - it would be impossible to notify about afterAll anyway.
+ *
+ * @param context context object providing access to all required objects (junit context, injector,
+ * test support, etc.)
+ */
+ default void stopped(final EventContext context) {
+ // empty default
+ }
+}
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/LambdaTestListener.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/LambdaTestListener.java
new file mode 100644
index 00000000..2f21a8ca
--- /dev/null
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/LambdaTestListener.java
@@ -0,0 +1,26 @@
+package ru.vyarus.dropwizard.guice.test.jupiter.env.listen.lambda;
+
+import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext;
+
+/**
+ * Lambda version for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener}. Requires
+ * {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.lambda.TestExecutionListenerLambdaAdapter}. Assumed to
+ * be used with {@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestEnvironmentSetup} object, when its declared
+ * as lambda itself and complete listenere implementation (
+ * {@link ru.vyarus.dropwizard.guice.test.jupiter.env.TestExtension#listen(
+ * ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener)}) would look clumsy.
+ *
+ * @author Vyacheslav Rusakov
+ * @since 20.02.2025
+ */
+@FunctionalInterface
+public interface LambdaTestListener {
+
+ /**
+ * Called on test event (see {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener})
+ * for events description.
+ *
+ * @param context context object providing access to all required objects
+ */
+ void onTestEvent(EventContext context);
+}
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/ListenerEvent.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/ListenerEvent.java
new file mode 100644
index 00000000..ad1ce5e5
--- /dev/null
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/ListenerEvent.java
@@ -0,0 +1,12 @@
+package ru.vyarus.dropwizard.guice.test.jupiter.env.listen.lambda;
+
+/**
+ * Test listener event definition for
+ * {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.lambda.LambdaTestListener}.
+ *
+ * @author Vyacheslav Rusakov
+ * @since 20.02.2025
+ */
+public enum ListenerEvent {
+ Started, Stopped, BeforeAll, AfterAll, BeforeEach, AfterEach
+}
diff --git a/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/TestExecutionListenerLambdaAdapter.java b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/TestExecutionListenerLambdaAdapter.java
new file mode 100644
index 00000000..0b02bb7d
--- /dev/null
+++ b/dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/env/listen/lambda/TestExecutionListenerLambdaAdapter.java
@@ -0,0 +1,66 @@
+package ru.vyarus.dropwizard.guice.test.jupiter.env.listen.lambda;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Multimap;
+import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext;
+import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener;
+
+/**
+ * An adapter for {@link ru.vyarus.dropwizard.guice.test.jupiter.env.listen.TestExecutionListener} to be able to
+ * register each listener method with a lambada (more suitable for builder style, rather than direct interface
+ * implementation).
+ *
+ * @author Vyacheslav Rusakov
+ * @since 20.02.2025
+ */
+public class TestExecutionListenerLambdaAdapter implements TestExecutionListener {
+
+ private final Multimap