Fusion Testing
Fusion Testing provides a way to start/stop a container for your tests and get injections in your test classes through method parameters or fields (still marked with @Fusion
).
Example
@FusionSupport
class Mytest {
@Test
void run(@Fusion final MyService service) {
// ...
}
}
Runner flavors
There are two runner flavors:
-
FusionSupport
which starts and stops the container per test class, -
MonoFusionSupport
which starts and stops the container per JVM - faster but does not isolate all classes.
using a custom JUnit 5 Extension
where you set system properties in a static block, you can configure the container before it starts. It is recommended to combine it in a custom annotation to control the ordering and ease the usage:
@Target(TYPE)
@Retention(RUNTIME)
@MonoFusionSupport
@ExtendsWith(MyAppSupport.MyConf.class)
public @interface MyAppSupport {
class MyConf implements Extension {
static {
// do the configuration
System.setProperty("....", "....");
}
}
}
Then simply replace fusion annotation by MyAppSupport
.
Alternatively you can register a test ConfigurationSource
bean if you prefer but this extension option enables to also start global services like dependencies mock or a database.
Note that if you need to exclude some module from the discovery (which uses ServiceLoader
), mono extension readsyupiik.fusion.mono.modules.discovery.excluded
system property which takes a comma separated list of fully qualified names.
Testing a Launcher application
Particularly for CLI applications (using an Launcher
and an Awaiter
getting Args
injected for example), you can use @FusionCLITest
as a replacement of @Test
. It will enable you to get an automatic execution of Launcher
based on the annotation args
value and to get injected Stdout
and Stderr
to validate the outputs.
@FusionCLITest(args = {"test", "run"})
void run(final Stdout stdout) {
assertEquals("...", stdout.content().strip());
}
TIP
|
|
Utilities
Additionally to the main container friendly extensions, fusion-testing
also provides a few utilities to ease writing tests.
TestClient
TestClient
is a bean which is active when you use fusion-json
at least and likely fusion-http-server
.
It autoconfigures the base URI of the server - even when port is set to 0
(random) and enables to use HttpClient
like API with:
-
Logging of the request/response (uses
fusion-httpclient
is present), -
Hides exception handling (synchronous API, aka
send
) to simplify code writing in tests, -
Provide
jsonRpc
JUnit5 integration to ease JSON-RPC tests.
@Test
void myTest(@Fusion final TestClient client) { (1)
final var res = client.jsonRpcRequest( (2)
"my.jsonrpc.method", (3)
Map.of("id", id.value())); (4)
final var order = res
.asJsonRpc() (5)
.success() (6)
.as(Order.class); (7)
assertAll(
() -> assertEquals(200, res.statusCode()),
() -> assertInsertedOrder(order, id, res::body));
}
-
Inject the
TestClient
as a standard fusion bean - requires a fusion support annotation, - Call a JSON-RPC method just providing its method name and parameters,
- The JSON-RPC method name,
- The JSON-RPC method params,
-
Convert the
HttpResponse
to a JSON-RPC response helper, -
Asserts the JSON-RPC response has no
error
, -
Maps the
result
as an object - compatible withfusion-json
.
Before/After method task
JUnit5 has @BeforeEach
/@AfterEach
callbacks but they are not per methods and related to the class - state - more then the test itself. It is also not directly related to fusion IoC beans.
To solve that pitfall, @Task
enables to reference some code - task - which can be a fusion bean or not and call it before or after a test method execution.
Here is how it can look like:
@Test
@Task(phase = BEFORE, value = InsertMyData.class) (1)
@Task(phase = AFTER, value = CleanMyData.class) (2)
void run(@TaskResult(InsertMyData.class) final MyData data) { (3)
// ...
}
-
Before the test run the bean
InsertMyData
, -
After the test run the bean
CleanMyData
, -
Inject the result of
InsertMyData
as first parameter.
@Task(value)
must implement Task.Supplier
which can be a java Supplier<T>
- which, for BEFORE
tasks will enable to inject some state as parameter referencing the bean class, or a Runnable
- mainly for AFTER
tasks.
Here is a sample BEFORE
task:
@DefaultScoped
public class BeforeTask implements Task.Supplier<String> {
private final MyService service;
// constructor...
@Override
public MyData get() {
return service.createData();
}
}
An after task will often be like:
@DefaultScoped
public class AfterTask implements Task.Supplier<String> {
private final MyService service;
// constructor...
@Override
public void run() {
service.clean();
}
}
What is interesting is that, since tasks are beans, you can use an @ApplicationScoped
bean to link both before/after tasks and for example store the before identifiers to delete in the after phase.