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'
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)
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 helperDropwizardTestSupport
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")
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();