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

args are made available to Configuration thanks a dedicated ConfigurationSource which supports these style of arguments (all leading to foo=bar mapping): --foo bar, -foo bar, foo bar, --foo=bar, -foo=bar, foo=bar. A particular arg starting by fusion-properties will be replaced by loading its value (as Properties, it can be inline or a file path - preferred).

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));
}
  1. Inject the TestClient as a standard fusion bean - requires a fusion support annotation,
  2. Call a JSON-RPC method just providing its method name and parameters,
  3. The JSON-RPC method name,
  4. The JSON-RPC method params,
  5. Convert the HttpResponse to a JSON-RPC response helper,
  6. Asserts the JSON-RPC response has no error ,
  7. Maps the result as an object - compatible with fusion-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)
    // ...
}
  1. Before the test run the bean InsertMyData ,
  2. After the test run the bean CleanMyData ,
  3. 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.