Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to get an HttpClient when singletons are eagerly initialised #716

Merged
merged 6 commits into from
Feb 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/graalvm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ jobs:
version: ${{ matrix.graalvm }}
java-version: ${{ matrix.java }}
components: 'native-image'
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build with Gradle
run: |
if ./gradlew tasks --no-daemon --all | grep -w "testNativeImage"
Expand Down
24 changes: 21 additions & 3 deletions src/main/docs/guide/introduction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,31 @@ This is achieved through a set of annotations:

These annotations use internal Micronaut features and do not mock any part of Micronaut itself. When you run a test within `@MicronautTest` it is running your real application.

In some tests you may need a reference to the `ApplicationContext` and/or the `EmbeddedServer` (for example, to create an instance of an `HttpClient`). Rather than defining these as properties of the test (such as a `@Shared` property in Spock), when using `@MicronautTest` you can reference the server/context that was started up for you, and inject them directly in your test.
In some tests you may need a reference to the `ApplicationContext` and/or the `EmbeddedServer` (for example, to create an instance of an `HttpClient`). Rather than defining these as properties of the test (such as a `@Shared` property in Spock), when using `@MicronautTest` you can reference the server/context that was started up for you, and inject them directly in your test.

[source,groovy]
----
@Inject
@Inject
EmbeddedServer server //refers to the server that was started up for this test suite

@Inject
@Inject
ApplicationContext context //refers to the current application context within the scope of the test
----

### Eager Singleton Initialization

If you enable https://docs.micronaut.io/latest/guide/index.html#eagerInit[eager singleton initialization] in your application, the Micronaut Framework eagerly initializes all singletons at startup time. This can be useful for applications that need to perform some initialization at startup time, such as registering a bean with a third party library.

However, as tests annotated with `@MicronautTest` are implicitly in the `Singleton` scope, this can cause problems injecting some beans (for example an `HttpClient`) into your test class.

To avoid this, you can either disable eager singleton initialization for your tests, or you will need to manually get an instance of the bean you would normally inject. As an example, to get an `HttpClient` you could do:

[source,java]
.Using an HttpClient in a test with eager singleton initialization enabled
----
include::test-junit5/src/test/java/io/micronaut/test/junit5/EagerInitializationTest.java[tags=eager, indent=0]
----

<1> Inject the `EmbeddedServer` as normal
<2> Create a `Supplier` that will create the `HttpClient` when it is first called
<3> Use the `Supplier` to get the `HttpClient` and make the request
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.micronaut.test.junit5;

import io.micronaut.context.DefaultApplicationContextBuilder;
import io.micronaut.context.annotation.Property;
import io.micronaut.context.annotation.Requires;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.util.SupplierUtil;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.HttpClient;
import io.micronaut.runtime.server.EmbeddedServer;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.function.Supplier;

@MicronautTest(contextBuilder = EagerInitializationTest.EagerContextBuilder.class)
@Property(name = "spec.name", value = "EagerInitializationTest")
class EagerInitializationTest {

// tag::eager[]
@Inject
EmbeddedServer server; // <1>

Supplier<HttpClient> client = SupplierUtil.memoizedNonEmpty(() ->
server.getApplicationContext().createBean(HttpClient.class, server.getURL())); // <2>

@Test
void testEagerSingleton() {
Assertions.assertEquals("eager", client.get().toBlocking().retrieve("/eager")); // <3>
}
// end::eager[]

@Requires(property = "spec.name", value = "EagerInitializationTest")
@Controller("/eager")
public static class EagerController {
@Get
String test() {
return "eager";
}
}

@Introspected
static class EagerContextBuilder extends DefaultApplicationContextBuilder {
public EagerContextBuilder() {
eagerInitSingletons(true);
}
}
}