Skip to content

JUnit 5

Junit 5 user guide

Setup

You will need the following dependencies (assuming BOM used for versions management):

testImplementation 'io.dropwizard:dropwizard-testing'
testImplementation 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter'

Tip

If you already have junit4 or spock tests, you can activate vintage engine so all tests could work together with junit 5:

testRuntimeOnly 'org.junit.vintage:junit-vintage-engine'

Note

In gradle you need to explicitly activate junit 5 support with

test {
    useJUnitPlatform()
    ...
}                    

Warning

Junit 5 annotations are different from junit4, so if you have both junit 5 and junit 4 make sure correct classes (annotations) used for junit 5 tests:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

Dropwizard extensions compatibility

Guicey extensions could be used together with dropwizard extensions. This may be required only in edge cases when multiple applications startup is required.

For example:

// run app (injector only)
@TestGuiceyApp(App.class)
// activate dropwizard extensions
@ExtendWith(DropwizardExtensionsSupport.class)
public class ClientSupportGuiceyTest {

    // Use dropwizard extension to start a separate server
    // It might be the same application or different 
    // (application instances would be different in any case)
    static DropwizardAppExtension app = new DropwizardAppExtension(App.class);

    @Test
    void testLimitedClient(ClientSupport client) {
        Assertions.assertEquals(200, client.target("http://localhost:8080/dummy/")
                .request().buildGet().invoke().getStatus());
    }
}

Info

There is a difference in extensions implementation.

Dropwizard extensions work as: junit extension @ExtendWith(DropwizardExtensionsSupport.class) looks for fields implementing DropwizardExtension (like DropwizardAppExtension) and start/stop them according to test lifecycle.

Guicey extensions implemented as separate junit extensions (and only hook fields are manually searched). Also, guciey extensions implement junit parameters injection (for test and lifecycle methods).

Extensions

  • @TestGuiceyApp - for lightweight tests (without starting web part, only guice context)
  • @TestDropwizardApp - for complete integration tests

For most tests @TestGuiceyApp assumed to be used as it only starts guice injector (which is much faster than complete application startup). Such tests are ideal for testing business logic (services).

@TestDropwizardApp (full integration test) used only to check web endpoints and full workflow (assuming all business logic was already tested with lightweight tests)

Both extensions allow using injections directly in test fields.

Extensions could be used under junit parallel execution (no side effects).

Alternative declaration might be used for deferred configuration or starting application for each test method.

Pre-configured http client might be used for calling test application endpoints (or external).

Test environment might be prepared with setup objects and application might be re-configured with hooks

Testing core logic

@TestGuiceyApp runs all guice logic without starting jetty (so resources, servlets and filters will not be available). Managed objects will still be handled correctly.

@TestGuiceyApp(MyApplication.class)
public class AutoScanModeTest {

    @Inject 
    MyService service;

    @Test
    public void testMyService() {        
        Assertions.assertEquals("hello", service.getSmth());     
    }

Also, injections work as method parameters:

@TestGuiceyApp(MyApplication.class)
public class AutoScanModeTest {

    public void testMyService(MyService service) {        
        Assertions.assertEquals("hello", service.getSmth());     
    }

Application started before all tests in annotated class and stopped after them.

Testing web logic

@TestDropwizardApp is useful for complete integration testing (when web part is required):

@TestDropwizardApp(MyApplication.class)
class WebModuleTest {

    @Inject 
    MyService service

    @Test
    public void checkWebBindings(ClientSupport client) {

        Assertions.assertEquals("Sample filter and service called", 
            client.targetMain("servlet").request().buildGet().invoke().readEntity(String.class));

        Assertions.assertTrur(service.isCalled());

Random ports

In order to start application on random port you can use configuration shortcut:

@TestDropwizardApp(value = MyApplication.class, randomPorts = true)

Note

Random ports setting override exact ports in configuration:

@TestDropwizardApp(value = MyApplication, 
                  config = 'path/to/my/config.yml', 
                  randomPorts = true)
Also, random ports support both server types (default and simple)

Real ports could be resolved with ClientSupport object.

Rest mapping

Normally, rest mapping configured with server.rootMapping=/something/* configuration, but if you don't use custom configuration class, but still want to re-map rest, shortcut could be used:

@TestDropwizardApp(value = MyApplication.class, restMapping="something")

In contrast to config declaration, attribute value may not start with '/' and end with '/*' - it would be appended automatically.

This option is only intended to simplify cases when custom configuration file is not yet used in tests (usually early PoC phase). It allows you to map servlet into application root in test (because rest is no more resides in root). When used with existing configuration file, this parameter will override file definition.

Guice injections

Any guice bean could be injected directly into the test field:

@Inject
SomeBean bean

This will work even for not declared (in guice modules) beans (JIT injection will occur).

To better understand injection scopes look the following test:

// one application instance started for all test methods
@TestGuiceyApp(AutoScanApplication.class)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class InjectionScopeTest {

    // new instance injected on each test
    @Inject
    TestBean bean;

    // the same context used for all tests (in class), so the same bean instance inserted before each test
    @Inject
    TestSingletonBean singletonBean;

    @Test
    @Order(1)
    public void testInjection() {
        bean.value = 5;
        singletonBean.value = 15;

        Assertions.assertEquals(5, bean.value);
        Assertions.assertEquals(15, singletonBean.value);

    }

    @Test
    @Order(2)
    public void testSharedState() {

        Assertions.assertEquals(0, bean.value);
        Assertions.assertEquals(15, singletonBean.value);
    }

    // bean is in prototype scope
    public static class TestBean {
        int value;
    }

    @Singleton
    public static class TestSingletonBean {
        int value;
    }
}

Note

Guice AOP will not work on test methods (because test instances are not created by guice).

Parameter injection

Any declared guice bean may be injected as test method parameter:

@Test
public void testSomthing(DummyBean bean) 

(where DummyBean is manually declared in some module or requested as a dependency (JIT-instantiated) during injector creation).

For unknown beans injection (not declared and not used during startup) special annotation must be used:

@Test
public void testSomthing(@Jit TestBean bean) 

Info

Additional annotation required because you may use other junit extensions providing their own parameters, which guicey extension should not try to handle. That's why not annotated parameters verified with existing injector bindings.

Qualified and generified injections will also work:

@Test
public void testSomthing(@Named("qual") SomeBean bean,
                         TestBean<String> generifiedBean,
                         Provider<OtherBean> provider) 

Also, there are special objects available as parameters:

  • Application or exact application class (MyApplication)
  • ObjectMapper
  • ClientSupport application web client helper
  • DropwizardTestSupport test support object used internally

Note

Parameter injection will work on test methods as well as lifecyle methods (beforeAll, afterEach etc.)

Example:

@TestDropwizardApp(AutoScanApplication.class)
public class ParametersInjectionDwTest {

    public ParametersInjectionDwTest(Environment env, DummyService service) {
        Preconditions.checkNotNull(env);
        Preconditions.checkNotNull(service);
    }

    @BeforeAll
    static void before(Application app, DummyService service) {
        Preconditions.checkNotNull(app);
        Preconditions.checkNotNull(service);
    }

    @BeforeEach
    void setUp(Application app, DummyService service) {
        Preconditions.checkNotNull(app);
        Preconditions.checkNotNull(service);
    }

    @AfterEach
    void tearDown(Application app, DummyService service) {
        Preconditions.checkNotNull(app);
        Preconditions.checkNotNull(service);
    }

    @AfterAll
    static void after(Application app, DummyService service) {
        Preconditions.checkNotNull(app);
        Preconditions.checkNotNull(service);
    }

    @Test
    void checkAllPossibleParams(Application app,
                                AutoScanApplication app2,
                                Configuration conf,
                                TestConfiguration conf2,
                                Environment env,
                                ObjectMapper mapper,
                                Injector injector,
                                ClientSupport client,
                                DropwizardTestSupport support,
                                DummyService service,
                                @Jit JitService jit) {
        assertNotNull(app);
        assertNotNull(app2);
        assertNotNull(conf);
        assertNotNull(conf2);
        assertNotNull(env);
        assertNotNull(mapper);
        assertNotNull(injector);
        assertNotNull(client);
        assertNotNull(support);
        assertNotNull(service);
        assertNotNull(jit);
        assertEquals(client.getPort(), 8080);
        assertEquals(client.getAdminPort(), 8081);
    }

    public static class JitService {

        private final DummyService service;

        @Inject
        public JitService(DummyService service) {
            this.service = service;
        }
    }
}

Tip

DropwizardTestSupport and ClientSupport objects are also available with a static calls (in the same thread):

DropwizardTestSupport support = TestSupport.getContext();
ClientSupport client = TestSupport.getContextClient();

Client

Both extensions prepare special jersey client instance which could be used for web calls. It is mostly useful for complete web tests to call rest services and servlets.

@Test
void checkRandomPorts(ClientSupport client) {
    Assertions.assertNotEquals(8080, client.getPort());
    Assertions.assertNotEquals(8081, client.getAdminPort());
}

Client object provides:

  • Access to JerseyClient object (for raw calls)
  • Shortcuts for querying main, admin or rest contexts (it will count the current configuration automatically)
  • Shortcuts for base main, admin or rest contexts base urls (and application ports)

Example usages:

// GET {rest path}/some
client.targetRest("some").request().buildGet().invoke()

// GET {main context path}/servlet
client.targetMain("servlet").request().buildGet().invoke()

// GET {admin context path}/adminServlet
client.targetAdmin("adminServlet").request().buildGet().invoke()

Tip

All methods above accepts any number of strings which would be automatically combined into correct path:

client.targetRest("some", "other/", "/part")
would be correctly combined as "/some/other/part/"

As you can see test code is abstracted from actual configuration: it may be default or simple server with any contexts mapping on any ports - target urls will always be correct.

Response res = client.targetRest("some").request().buildGet().invoke()

Assertions.assertEquals(200, res.getStatus())
Assertions.assertEquals("response text", res.readEntity(String)) 

Also, if you want to use other client, client object can simply provide required info:

client.getPort()        // app port (8080)
client.getAdminPort()   // app admin port (8081)
client.basePathMain()   // main context path (http://localhost:8080/)
client.basePathAdmin()  // admin context path (http://localhost:8081/)
client.basePathRest()   // rest context path (http://localhost:8080/)

Raw client usage:

// call completely external url
client.target("http://somedomain:8080/dummy/").request().buildGet().invoke()

Warning

Client object could be injected with both dropwizard and guicey extensions, but in case of guicey extension, only raw client could be used (because web part not started all other methods will throw NPE)

Client factory

Internal JerseyClient creation could be customized with TestClientFactory implementation.

Default implementation (DefaultTestClientFactory) applies timeouts and auto-registers multipart support if dropwizard-forms module is available in classpath.

Custom implementation could be specified directly in the test annotation:

@TestDropwizardApp(value = MyApp.class, clientFactory = CustomTestClientFactory.class)

Configuration

For both extensions you can configure application with external configuration file:

@TestGuiceyApp(value = MyApplication.class,
    config = "path/to/my/config.yml"
public class ConfigOverrideTest {

Or just declare required values:

@TestGuiceyApp(value = MyApplication.class,
    configOverride = {
            "foo: 2",
            "bar: 12"
    })
public class ConfigOverrideTest {

(note that overriding declaration follows yaml format "key: value")

Or use both at once (here overrides will override file values):

@TestGuiceyApp(value = MyApplication.class,
    config = 'path/to/my/config.yml',
    configOverride = {
            "foo: 2",
            "bar: 12"
    })
class ConfigOverrideTest {

Deferred configuration

If you need to configure value, supplied by some other extension, or value may be resolved only after test start, then static overrides declaration is not an option. In this case use alternative extensions declaration which provides additional config override methods:

@RegisterExtension
@Order(1)
static FooExtension ext = new FooExtension();

@RegisterExtension
@Order(2)
static TestGuiceyAppExtension app = TestGuiceyAppExtension.forApp(AutoScanApplication.class)
        .config("src/test/resources/ru/vyarus/dropwizard/guice/config.yml")
        .configOverrides("foo: 1")
        .configOverride("bar", () -> ext.getValue())
        .configOverrides(new ConfigOverrideValue("baa", () -> "44"))
        .create();

In most cases configOverride("bar", () -> ext.getValue()) would be enough to configure a supplier instead of static value.

In more complex cases, you can use custom implementations of ConfigOverride.

Guicey have to accept only ConfigOverride objects implementing custom ru.vyarus.dropwizard.guice.test.util.ConfigurablePrefix interface. In order to support parallel tests guicey generates unique config prefix for each test (because all overrides eventually stored to system properties) and so it needs a way to set this prefix into custom ConfigOverride objects.

Configuration from 3rd party extensions

If you have junit extension (e.g. which starts db for test) and you need to apply configuration overrides from that extension, then you should simply store required values inside junit storage:

public class ConfigExtension implements BeforeAllCallback {

    @Override
    public void beforeAll(ExtensionContext context) throws Exception {
        // do something and then store value
        context.getStore(ExtensionContext.Namespace.GLOBAL).put("ext1", 10);
    }
}

And map overrides directly from store using configOverrideByExtension method:

@ExtendWith(ConfigExtension.class)
public class SampleTest {

    @RegisterExtension
    static TestGuiceyAppExtension app = TestGuiceyAppExtension.forApp(App.class)
            .configOverrideByExtension(ExtensionContext.Namespace.GLOBAL, "ext1")
            .create();
}

Here, value applied by extension under key ext1 would be applied to configuration ext1 path. If you need to use different configuration key:

.configOverrideByExtension(ExtensionContext.Namespace.GLOBAL, "ext1", "key")

Tip

You can use setup objects instead of custom junit extensions for test environment setup

Test environment setup

It is often required to prepare test environment before starting dropwizard application. Normally, such cases require writing custom junit extensions. In order to simplify environment setup, guicey provides TestEnviromentSetup interface.

Setup objects are called before application startup and could directly apply (through parameter) configuration overrides and hooks.

For example, suppose you need to set up a database before test:

public class TestDbSetup implements TestEnvironmentSetup {

    @Override
    public Object setup(TestExtension extension) {
        // pseudo code
        Db db = DbFactory.startTestDb();
        // register required configuration
        extension
                .configOverride("database.url", ()-> db.getUrl())
                .configOverride("database.user", ()-> db.getUser())
                .configOverride("database.password", ()-> db.getPassword);
        // assuming object implements Closable
        return db;
    }
}

It is not required to return anything, only if something needs to be closed after application shutdown: objects other than Closable (AutoClosable) or org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource simply ignored. This approach (only one method) simplifies interface usage with lambdas.

Setup object might be declared in extension annotation:

@TestGuiceyApp(value=App.class, setup=TestDbSetup.class)

Or in manual registration:

@RegisterExtension
TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(App.class)
        // as class
        .setup(TestDbSetup.class)
        // or as instance
        .setup(new TestDbSetup())

Or with lambda:

.setup(ext -> {
        Db db = new Db();
        ext.configOverride("db.url", ()->db.getUrl())
        return db;
})

Setup fields

Alternatively, setup objects might be declared simply in test fields:

@EnableSetup
static TestEnvironmentSetup db = ext -> {
            Db db = new Db();
            ext.configOverride("db.url", ()->db.getUrl())
            return db;
        };

or

@EnableSetup
static TestDbSetup db = new TestDbSetup()

This could be extremely useful if you need to unify setup logic for multiple tests, but use different extension declarations in test. In this case simply move field declaration into base test class:

public abstract class BaseTest {

    @EnableSetup
    static TestDbSetup db = new TestDbSetup();
}

Note

To avoid confusion with guicey hooks: setup object required to prepare test environment before test (and apply required configurations) whereas hooks is a general mechanism for application customization (not only in tests). Setup objects are executed before application startup (before DropwizardTestSupport object creation) and hooks are executed by started application.

Application test modification

You can use hooks to customize application.

In both extensions annotation hooks could be declared with attribute:

@TestDropwizardApp(value = MyApplication.class, hooks = MyHook.class)

or

@TestGuiceyApp(value = MyApplication.class, hooks = MyHook.class)

Where MyHook is:

public class MyHook implements GuiceyConfigurationHook {}

Hook fields

Alternatively, you can declare hook directly in test field:

@EnableHook
static GuiceyConfigurationHook HOOK = builder -> builder.modules(new DebugModule());

Any number of fields could be declared. The same way hook could be declared in base test class:

public abstract class BaseTest {

    // hook in base class
    @EnableHook
    static GuiceyConfigurationHook BASE_HOOK = builder -> builder.modules(new DebugModule());
}

@TestGuiceyApp(value = App.class, hooks = SomeOtherHook.class)
public class SomeTest extends BaseTest {

    // Another hook
    @EnableHook
    static GuiceyConfigurationHook HOOK = builder -> builder.modules(new DebugModule2());
}

All 3 hooks will work.

Extension configuration unification

It is a common need to run multiple tests with the same test application configuration (same config overrides, same hooks etc.). Do not configure it in each test, instead move extension configuration into base test class:

@TestGuiceyApp(...)
public abstract class AbstractTest {
    // here might be helper methods
}

And now all test classes should simply extend it:

public class Test1 extends AbstractTest {

    @Inject
    MyService service;

    @Test
    public void testSomething() { ... }
}

If you use manual extension configuration (through field), just replace annotation in base class with manual declaration - approach would still work.

Reuse application between tests

In some cases it is preferable to start application just once and use for all tests (e.g. due to long startup or time-consuming environment preparation).

In order to use the same application instance, extension declaration must be performed in base test class and reuseApplication flag must be enabled:

@TestGuiceyApp(value = Application.class, reuseApplication = true)
public abstract class BaseTest {}

or

public abstract class BaseTest {
    @RegisterExtension
    static TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(App.class)
            .reuseApplication()
            .create();

}

The same will work for dropwizard extension (@TestDropwizardApp and TestDropwizardAppExtension).

Important

Application instance re-use is not enabled by default for backwards compatibility (for cases when base class declaration already used).

There might be multiple base test classes declaring reusable applications: different global applications would be started for each declaration (allowing you to group tests requiring different applications)

Global application would be closed after all tests execution (with test engine shutdown).

In essence, reusable application "stick" to declaration in base class, so all tests, extending base class "inherit" the same declaration and so the same application (when reuse enabled).

Tip

Reusable applications may be used together with tests, not extending base class and using guicey extensions. Such tests would simply start a new application instance. Just be sure to avoid port clashes when using reusable dropwizard apps (by using randomPorts option).

@EnableSetup and @EnableHook fields are also supported for reusable applications. But declare all such fields on base class level (or below) because otherwise only fields declared on first started test would be used. Warning would be printed if such fields used (or ignored because reusable app was already started by different test).

Parallel execution

Junit parallel tests execution could be activated with properties file junit-platform.properties located at test resources root:

junit.jupiter.execution.parallel.enabled = true
junit.jupiter.execution.parallel.mode.default = concurrent

Note

In order to avoid config overriding collisions (because all overrides eventually stored to system properties) guicey generates unique property prefixes in each test.

To avoid port collisions in dropwizard tests use randomPorts option.

Alternative declaration

Both extensions could be declared in fields:

@RegisterExtension
static TestDropwizardAppExtension app = TestDropwizardAppExtension.forApp(AutoScanApplication.class)
        .config("src/test/resources/ru/vyarus/dropwizard/guice/config.yml")
        .configOverrides("foo: 2", "bar: 12")
        .randomPorts()
        .hooks(Hook.class)
        .hooks(builder -> builder.disableExtensions(DummyManaged.class))
        .create();

The only difference with annotations is that you can declare hooks and setup objects as lambdas directly (still hooks in static fields will also work).

@RegisterExtension
static TestGuiceyAppExtension app = TestGuiceyAppExtension.forApp(AutoScanApplication.class)
        ...

This alternative declaration is intended to be used in cases when guicey extensions need to be aligned with other 3rd party extensions: in junit you can order extensions declared with annotations (by annotation order) and extensions declared with @RegisterExtension (by declaration order). But there is no way to order extension registered with @RegisterExtension before annotation extension.

So if you have 3rd party extension which needs to be executed BEFORE guicey extensions, you can use field declaration.

Note

Junit 5 intentionally shuffle @RegisterExtension extensions order, but you can always order them with @Order annotation.

Start application by test method

When you declare extensions with annotations or with @RegisterExtension in static fields, application would be started before all test methods and shut down after last test method.

If you want to start application for each test method then declare extension in non-static field:

@RegisterExtension
TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(App.class).create()

// injection would be re-newed for each test method
@Inject Bean bean;

@Test
public void test1() {
    Assertions.assertEquals(0, bean.value);
    // changing value to show that bean was reset between tests
    bean.value = 10    
}

@Test
public void test2() {
    Assertions.assertEquals(0, bean.value);
    bean.value = 10
}

Also, @EnableHook and @EnableSetup fields might also be not static (but static fields would also work) in this case:

@RegisterExtension
TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(App.class).create()

@EnableSetup
MySetup setup = new MySetup()

Debug

Debug option could be activated on extensions in order to print

registered setup objects and hooks (registered in test):

Guicey test extensions (Test2.):

    Setup objects = 
        HookObjectsLogTest$Test2$$Lambda$349/1644231115 (r.v.d.g.t.j.hook)                  @EnableSetup field Test2.setup

    Test hooks = 
        HookObjectsLogTest$Base$$Lambda$341/1127224355 (r.v.d.g.t.j.hook)                   @EnableHook field Base.base1
        Ext1                         (r.v.d.g.t.j.h.HookObjectsLogTest)                     @RegisterExtension class
        HookObjectsLogTest$Test2$$Lambda$345/484589713 (r.v.d.g.t.j.hook)                   @RegisterExtension instance
        Ext3                         (r.v.d.g.t.j.h.HookObjectsLogTest)                     HookObjectsLogTest$Test2$$Lambda$349/1644231115 class
        HookObjectsLogTest$Test2$$Lambda$369/1911152052 (r.v.d.g.t.j.hook)                  HookObjectsLogTest$Test2$$Lambda$349/1644231115 instance
        HookObjectsLogTest$Test2$$Lambda$350/537066525 (r.v.d.g.t.j.hook)                   @EnableHook field Test2.ext1

which prints registered objects in the execution order and with registration source in the right.

And applied configuration overrides:

Applied configuration overrides (Test1.): 

                      foo = 1

Important

Configuration overrides printed after application startup because they are extracted from system properties (to guarantee exact used value), which is possible to analyze only after DropwizardTestSupport#before() call.

Note

Configuration prefix for system properties is shown in brackets: (Test1.). It simplifies investigation in case of concurrent tests.

Debug could be activated by annotation:

@TestGuiceyApp(value = App.class, debug = true)

By builder:

@RegisterExtension
TestGuiceyAppExtension ext = TestGuiceyAppExtension.forApp(App)
        .debug()
        .create()

By setup object:

@EnableSetup
static TestEnvironmentSetup db = ext -> {
            ext.debug();
        };

And using system property:

-Dguicey.extensions.debug=true

There is also a shortcut for enabling system property:

TestSupport.debugExtensions()

Junit nested classes

Junit natively supports nested tests.

Guicey extensions affects all nested tests below declaration (nesting level is not limited):

@TestGuiceyApp(AutoScanApplication.class)
public class NestedPropagationTest {

    @Inject
    Environment environment;

    @Test
    void checkInjection() {
        Assertions.assertNotNull(environment);
    }

    @Nested
    class Inner {

        @Inject
        Environment env; // intentionally different name

        @Test
        void checkInjection() {
            Assertions.assertNotNull(env);
        }
    }
}

Note

Nested tests will use exactly the same guice context as root test (application started only once).

Extension declared on nested test will affect all sub-tests:

public class NestedTreeTest {

    @TestGuiceyApp(AutoScanApplication.class)
    @Nested
    class Level1 {

        @Inject
        Environment environment;

        @Test
        void checkExtensionApplied() {
            Assertions.assertNotNull(environment);
        }

        @Nested
        class Level2 {
            @Inject
            Environment env;

            @Test
            void checkExtensionApplied() {
                Assertions.assertNotNull(env);
            }

            @Nested
            class Level3 {

                @Inject
                Environment envr;

                @Test
                void checkExtensionApplied() {
                    Assertions.assertNotNull(envr);
                }
            }
        }
    }

    @Nested
    class NotAffected {
        @Inject
        Environment environment;

        @Test
        void extensionNotApplied() {
            Assertions.assertNull(environment);
        }
    }
}

This way nested tests allows you to use different extension configurations in one (root) class.

Note that extension declaration with @RegisterExtension on the root class field would also be applied to nested tests. Even declaration in non-static field (start application for each method) would also work.

Use interfaces to share tests

This is just a tip on how to execute same test method in different environments.

public class ClientSupportDwTest {

    interface ClientCallTest {
        // test to apply for multiple environments
        @Test
        default void callClient(ClientSupport client) {
            Assertions.assertEquals("main", client.targetMain("servlet")
                    .request().buildGet().invoke().readEntity(String.class));
        }
    }

    @TestDropwizardApp(App.class)
    @Nested
    class DefaultConfig implements ClientCallTest {

        @Test
        void testClient(ClientSupport client) {
            Assertions.assertEquals("http://localhost:8080/", client.basePathMain());
        }
    }

    @TestDropwizardApp(value = App.class, configOverride = {
            "server.applicationContextPath: /app",
            "server.adminContextPath: /admin",
    }, restMapping = "api")
    @Nested
    class ChangedDefaultConfig implements ClientCallTest {

        @Test
        void testClient(ClientSupport client) {
            Assertions.assertEquals("http://localhost:8080/app/", client.basePathMain());
        }
    }
}

Here test declared in ClientCallTest interface will be called for each nested test (one declaration - two executions in different environments).

Meta annotation

You can prepare meta annotation (possibly combining multiple 3rd party extensions):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@TestDropwizardApp(AutoScanApplication.class)
public @interface MyApp {
}

@MyApp
public class MetaAnnotationDwTest {

    @Test
    void checkAnnotationRecognized(Application app) {
        Assertions.assertNotNull(app);
    }   
}

OR you can simply use base test class and configure annotation there:

@TestDropwizardApp(AutoScanApplication.class)
public class BaseTest {}

public class ActualTest extends BaseTest {} 

Testing commands

Warning

Commands execution overrides System IO and so can't run in parallel with other tests!

Use @Isolated on such tests to prevent parallel execution with other tests

Command execution is usually a short-lived action, so it is not possible to write an extension for it. Command could be tested only with generic utility:

@Test
public class testCommand() {
    CommandResult result = TestSupport.buildCommandRunner(App.class)
            .run("cmd", "-p", "param");

    Assertions.assertTrue(result.isSuccessful());
}

Run command arguments are the same as real command arguments (the same Cli used for commands parsing). You can only omit configuration path and use builder instead:

    CommandResult result = TestSupport.buildCommandRunner(App.class)
            .config("path/to/config.yml")
            .configOverride("prop: 1")
            .run("cmd", "-p", "param");

Important

Command execution never throws an exception - any appeared exception would be inside resulted object.

Output is intercepted and could be used for assertions:

Assertions.assertTrue(result.getOutput().contains("some text"))

All special objects (like configuration, environment etc), created during command execution are all stored inside the result object (this is the only way to access them).

Commands requiring user input are also supported:

CommandResult result = TestSupport.buildCommandRunner(App.class)
        .consoleInputs("1", "two", "something else")
        .run("quiz")

This could be used to test any command (simple, configuration or environment), but injector would be created only for environment command.

Testing startup error

Warning

Commands execution overrides System IO and so can't run in parallel with other tests!

Use @Isolated on such tests to prevent parallel execution with other tests

Tests for application startup fail often required to check some startup conditions. The problem is that it's not enough to simply run the application with "bad" configuration file because on error application calls System.exit(1) (see Application.onFatalError)

Instead, you can use command run utility:

CommandResult result = TestSupport.buildCommandRunner(App.class)
        .runApp()

Note

Test framework-agnostic utilities provides simple utilities to run application (core or web). Could be useful when testing several applications interaction.

Environment variables

Warning

Such modifications are not suitable for parallel tests execution!

Use @Isolated on such tests to prevent parallel execution with other tests

To modify environment variables for test use system stubs library

testImplementation 'uk.org.webcompere:system-stubs-jupiter:2.1.3'
testImplementation 'org.objenesis:objenesis:3.3'
@ExtendWith(SystemStubsExtension.class)
public class MyTest {
    @SystemStub
    EnvironmentVariables ENV;
    @SystemStub
    SystemOut out;
    @SystemStub
    SystemProperties propsReset;

    @BeforeAll
    public void setup() {
        ENV.set("VAR", "1");
        System.setProperty("foo", "bar"); // OR propsReset.set("foo", "bar") - both works the same
    } 

    @Test
    public void test() {
        // here goes some test that requires custom environment and system property values

        // validating output
        Assertions.assertTrue(out.getTest().contains("some log message"));
    }
}

Pay attention that there is no need for cleanup: system properties and environment variables would be re-set automatically!

3rd party extensions integration

It is extremely simple in JUnit 5 to write extensions. If you do your own extension, you can easily integrate with guicey or dropwizard extensions: there are special static methods allowing you to obtain main test objects:

  • GuiceyExtensionsSupport.lookupSupport(extensionContext) -> Optional<DropwizardTestSupport>
  • GuiceyExtensionsSupport.lookupInjector(extensionContext) -> Optional<Injector>
  • GuiceyExtensionsSupport.lookupClient(extensionContext) -> Optional<ClientSupport>

For example:

public class MyExtension implements BeforeEachCallback {

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        Injector injector = GuiceyExtensionsSupport.lookupInjector(context).get();
        ...
    }
}

(guicey holds test state in junit test-specific storages and that's why test context is required)

Warning

There is no way in junit to order extensions, so you will have to make sure that your extension will be declared after guicey extension (@TestGuiceyApp or @TestDropwizardApp).

There is intentionally no direct api for applying configuration overrides from 3rd party extensions because it would be not obvious. Instead, you should always declare overridden value in extension declaration. Either use instance getter:

@RegisterExtension
static MyExtension ext = new MyExtension()

@RegisterExtension
static TestGuiceyAppExtension dw = TestGuiceyAppExtension.forApp(App.class)
        .configOverride("some.key", ()-> ext.getValue())
        .create()

Or store value inside junit store and then reference it:

@RegisterExtension
static TestGuiceyAppExtension app = TestGuiceyAppExtension.forApp(App.class)
        .configOverrideByExtension(ExtensionContext.Namespace.GLOBAL, "ext1")
        .create();