5.6.1 Release Notes¶
- Dropwizard 2.1 compatibility
- Junit 5 extensions enhancements
Dropwizard 2.1 compatibility¶
Release upgrades guicey to dropwizard 2.1.1
Java 8 issue¶
Dropwizard 2.1 use jackson blackbird by default now instead of afterburner. On java 8 you'll see the following warning on application startup:
WARN [2022-06-06 16:39:24,946] com.fasterxml.jackson.module.blackbird.BlackbirdModule: Unable to find Java 9+ MethodHandles.privateLookupIn. Blackbird is not performing optimally!
! java.lang.NoSuchMethodError: java.lang.invoke.MethodHandles.privateLookupIn(Ljava/lang/Class;Ljava/lang/invoke/MethodHandles$Lookup;)Ljava/lang/invoke/MethodHandles$Lookup;
! at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
! at java.lang.invoke.MemberName$Factory.resolve(MemberName.java:975)
! at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1000)
! ... 86 common frames omitted
! Causing: java.lang.NoSuchMethodException: no such method: java.lang.invoke.MethodHandles.privateLookupIn(Class,Lookup)Lookup/invokeStatic
! at java.lang.invoke.MemberName.makeAccessException(MemberName.java:871)
! at java.lang.invoke.MemberName$Factory.resolveOrFail(MemberName.java:1003)
! at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(MethodHandles.java:1386)
! at java.lang.invoke.MethodHandles$Lookup.findStatic(MethodHandles.java:780)
! at com.fasterxml.jackson.module.blackbird.util.ReflectionHack$Java9Up.init(ReflectionHack.java:39)
! at com.fasterxml.jackson.module.blackbird.util.ReflectionHack$Java9Up.<clinit>(ReflectionHack.java:34)
...
To fix this simply add afterburner jar into classpath and it would be used automatically instead of blackbird:
implementation 'com.fasterxml.jackson.module:jackson-module-afterburner:2.13.3'
(you may omit version if guicey or dropwizard BOM used)
Junit 5 extensions enhancements¶
Test support objects¶
Environment setup¶
In order to simplify environment setup in tests, new interface added:
public interface TestEnvironmentSetup {
Object setup(TestExtension extension);
}
For example, it might be used to setup test database:
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;
}
}
Motivation
Previously, additional junit extensions were required for such kind of setup, but there was a problem with configuration (because guicey generates system property key for each test and so it is not possible to configure application directly with system property. Also, it was problematic to move such initialization into base class because it could be done only with static fields. New interface should greatly simplify maintaining test environments.
Only configuration overrides and guicey hooks are allowed for registration.
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.
It is often required not only to start/create something before test, but also
properly stop/destroy it after. To do it simply return any Closable
(or AutoClosable
)
and it would be called just after application shutdown.
If no managed object required - you may return whatever else (even null), nothing would happen. This was done to simplify lambda declarations.
Registration¶
Registration is the same as with hooks:
setup
attribute in extension annotationssetup()
methods in extension builders (registered in fields)- Test fields, annotated with
@EnableSetup
Simple lambdas might be used for registration, for example:
@EnableSetup
static TestEnvironmentSetup db = ext -> {
Db db = new Db();
ext.configOverride("db.url", ()->db.getUrl())
return db;
};
Field-based declaration might be useful when such initializations must be declared in base test class (and affect all tests).
Extensions debug¶
There is a new debug option added to guicey extensions. When enabled it prints 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
Setup objects and hooks are shown in execution order. Setup objects go first because they might also register hooks. Registration source hints are shown on the right. There should be enough information to clearly understand test initialization sequence.
Warning
Setup objects and hooks logging was introduced in guicey 5.6.0, and it was always logged there.
In 5.6.1 it is shown only when debug option is enabled. Also, originally it was logged
using logger and now printed to System.out
.
And prints all 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 activation¶
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()
@EnableHook field type¶
Before, it was impossible to use exact hook class type in field declaration:
@EnableHook
static GuiceyConfigurationHook hook = new MyHook();
But now any class could be used:
@EnableHook
static MyHook hook = new MyHook();
Extensions registration changes¶
Start application for each test method¶
It is now possible to start application before each test method:
@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);
bean.value = 10
}
@Test
public void test2() {
Assertions.assertEquals(0, bean.value);
bean.value = 10
}
Note that field is not static. In this case extension would be activated for each method.
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()
Configure application from 3rd party junit extension¶
Note
Generally, setup objects usage should be simpler then writing additional junit extensions for environment setup, but if you already have an extension, the following should simplify configuration.
3rd party junit extension should only store required values using junit storage and
they could be applied now with a new method configOverrideByExtension
:
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);
}
}
@ExtendWith(ConfigExtension.class)
public class SampleTest {
@RegisterExtension
static TestGuiceyAppExtension app = TestGuiceyAppExtension.forApp(App.class)
.configOverrideByExtension(ExtensionContext.Namespace.GLOBAL, "ext1")
.create();
}
Here junit extension stores value and guicey extension will retrieve and apply value from store. Configuration path and storage key are the same here, but they could be different:
.configOverrideByExtension(ExtensionContext.Namespace.GLOBAL, "storage.key", "config.path")
Apply configuration path 'config.path' value from junit storage under key storage.key
.
Small builder improvements¶
.hooks(Class)
method now accepts multiple hooks at once:
.hooks(Hook1.class, Hook2.class);
.configOverrides(String...)
method now could be called multiple times:
.configOverrides("foo:1", "bar:2")
.configOverrides("over:3")