Skip to content

Commit

Permalink
Documentation for Micronaut test extensions
Browse files Browse the repository at this point in the history
This commit adds user documentation for the new Micronaut Test
extensions available in test resources.

This is a follow up to #231 and #241
  • Loading branch information
melix committed May 4, 2023
1 parent 43fdc73 commit 8c299af
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 0 deletions.
74 changes: 74 additions & 0 deletions src/main/docs/guide/extensions-core.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
In order to use the core extension, you need to add this dependency to your build:

dependency:micronaut-test-resources-extensions-core[groupId="io.micronaut.testresources", scope="test"]

== Expanding the set of test properties

Micronaut Test resources is capable of supplying the values of properties which are missing from your configuration when you execute tests.
For example, if the property `datasources.default.url` is missing, then the service will be called, a database will be provisioned and made available to your application.
However, there is one assumption: these properties are only available once the application context is available.

In some cases, you may need to expand the set of properties available to tests _before_ the application under test is started (or before the application context is available).
In Micronaut, a test property can be supplied by implementing the `io.micronaut.test.support.TestPropertyProvider` interface, for example:

[source,java]
----
class MyTest implements TestPropertyProvider {
// ...
Map<String, String> getProperties() {
return Map.of(
"myapp.someProperty", "value"
);
}
}
----

However, a `TestPropertyProvider` cannot access values which are provided by Micronaut Test Resources, or values which are available in your configuration files, which means that you cannot compute a new property based on the value of other properties.

The Micronaut test core extension offers an alternative mechanism to solve this problem: you can annotate a test with `@TestResourcesProperties` and provide a couple arguments:

- the list of test resource properties which you need to compute your property
- a class which is responsible for deriving new values from the set of available test properties

To illustrate this, imagine a test which needs to access a RabbitMQ cluster.
Micronaut test resources will not provide a `rabbitmq.servers.product-cluster.port`, but does provide a `rabbitmq.url` property.
What we need is therefore a provider which will compute the value of `rabbitmq.servers.product-cluster.port` from that `rabbitmq.url` property.

This has to be done _before_ the application is started, so we can use `@TestResourcesProperties` for this.
First, annotate your test with:

[source,java]
----
@MicronautTest
@TestResourcesProperties(
value = "rabbitmq.uri",
providers = ConnectionSpec.RabbitMQProvider.class
)
class ConnectionSpec {
// ...
}
----

Next implement the `RabbitMQProvider` type (here it's done as a nested class in the test but there's no limitation about where to find it):

[source,java]
----
@ReflectiveAccess
public static class RabbitMQProvider implements TestResourcesPropertyProvider {
@Override
public Map<String, String> provide(Map<String, Object> testProperties) {
String uri = (String) testProperties.get("rabbitmq.uri");
return Map.of(
"rabbitmq.servers.product-cluster.port", String.valueOf(URI.create(uri).getPort())
);
}
}
----

A test resources property provider can access:

- the list of properties requested via the `value` of `@TestResourcesProperties`
- the list of properties which are available in your configuration files

In this case, we are computing the value of `rabbitmq.servers.product-cluster.port` from the `rabbitmq.uri` and return it as a single map entry, which will be added to the set of test properties.
76 changes: 76 additions & 0 deletions src/main/docs/guide/extensions-junit-platform.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
In order to use the JUnit Platform extension, you need to add this dependency to your build:

dependency:micronaut-test-resources-extensions-junit-platform[groupId="io.micronaut.testresources", scope="test"]

This extension is compatible with test frameworks which use JUnit Platform, which means in particular all test frameworks supported by Micronaut:

- https://junit.org/junit5/[JUnit 5]
- https://spockframework.org[Spock Framework]
- https://kotest.io/[Kotest]
== Isolating test resources

By default, Micronaut Test Resources will share test resources for a whole test suite.
For example, if 2 tests need a MySQL container, then the first test will start the container, and the next step will reuse that container.
The main advantage is that you don't pay the cost of starting a container twice (or more), which can dramatically speed up test suites.
The responsibility of shutting down resources is delegated to the build tools (e.g Gradle or Maven), which do this at the end of the build.
The advantage of this is that not only a test container can be shared by different tests of a test suite, but it can be shared by different tasks of a single build.
In particular, this allows sharing a container between JVM tests and native tests: the container will be started at the beginning of the build, and reused for native tests.

While this behavior is usually preferred, there are situations where you may want to isolate tests, for example:

1. when your test requires a dedicated container with a different configuration from the other tests
2. when it's difficult to clean up the state of a container after a test is executed
3. when your tests spawn many different containers and that resource consumption becomes too high

For example, you may have an application which runs tests on different databases.
By using the default behavior, it would mean that a database container would be started for each database, then left over even when the suite which tests that database is done.
In order to reduce consumption, you can now use the `@TestResourcesScope` annotation to declare the _scope of test resources_.

The easiest is to use an explicit name for a scope:

[source,java]
-----
@TestResourcesScope("my scope")
class MyTest {
@Test
void testSomething() {
// ...
}
}
-----

When using this annotation, all tests which are using the same scope will share the test resources of that scope.
Once the last test which requires that scope is finished, **test resources of that scope are automatically closed**.

In addition to an explicit name, it is possible to supply a naming strategy:

[source,java]
-----
@TestResourcesScope(namingStrategy = MyStrategy)
class MyTest {
@Test
void testSomething() {
// ...
}
}
-----

Test resources come with 2 built-in strategies:

- `ScopeNamingStrategy.TestClassName` will use the name of the test class as the scope, which means in practice that the resources are exclusive to this test class
- `ScopeNamingStrategy.PackageName` will use the name of the package of the test, which can be handy if all the tests which are in a common package share the same resources.

== Known limitations of @TestResourcesScope

- a test can only use single scope: it is not possible to have a test which requires multiple scopes, but nested test classes can override the scope of their parent test class
- scopes are handled _per process_ (e.g per JVM), which means that if 2 tests use the same scope but are executed on different JVMs, then the scope will be closed independently in these JVMs.

The 2d point is important to understand as the test resources service runs independently of your test suite.
This means in practice that if 2 tests executed in 2 different processes, then as soon as the first process has finished all tests which use that scope, the scope will be closed on the _common server_.
While test resources can recover from that situation by spawning a new test resource when the 2d process will need it, it is possible that a scope is closed _while a test is running in a different process_.

For this reason, we recommend to make sure that you only use this feature if you are not splitting your test suite on multiple processes, which can happen if you use `forkEvery` in Gradle or parallel test execution with the Surefire Maven plugin.
Similarly, using the test distribution plugin of Gradle will trigger the same problem.

In practice, this may not be an issue for your use cases, depending on how you use scopes.
2 changes: 2 additions & 0 deletions src/main/docs/guide/extensions.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Micronaut Test Resources also provides extensions to https://github.com/micronaut-projects/micronaut-test[Micronaut Test] to support more advanced use cases.
The core extension provides a way to expand the set of test properties available to tests, while the JUnit Platform extension provides ways to isolate resources needed by different tests.
6 changes: 6 additions & 0 deletions src/main/docs/guide/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ modules:
title: Hashicorp Vault
modules-testcontainers:
title: Generic Testcontainers support
extensions:
title: Micronaut Test extensions
extensions-core:
title: Core extension
extensions-junit-platform:
title: JUnit Platform
architecture:
title: Architecture
architecture-server:
Expand Down

0 comments on commit 8c299af

Please sign in to comment.