diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index ab8ff2b88e4dc..2f78ac10484ab 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -5,6 +5,7 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc //// [id="testing"] = Testing Your Application + include::_attributes.adoc[] :categories: core, native, tooling :summary: This guide covers testing in JVM mode, native mode, and injection of resources into tests @@ -13,7 +14,6 @@ include::_attributes.adoc[] :sectnumlevels: 4 :topics: getting-started,testing,tooling - Learn how to test your Quarkus Application. This guide covers: @@ -46,10 +46,10 @@ This guide assumes you already have the completed application from the `getting- == Recap of HTTP based Testing in JVM mode -If you have started from the Getting Started example you should already have a completed test, including the correct -tooling setup. +If you have started from the Getting Started example you should already have a completed test, including the correct tooling setup. In your build file you should see 2 test dependencies: + [role="primary asciidoc-tabs-sync-maven"] .Maven **** @@ -67,6 +67,7 @@ In your build file you should see 2 test dependencies: ---- **** + [role="secondary asciidoc-tabs-sync-gradle"] .Gradle **** @@ -80,8 +81,7 @@ dependencies { **** `quarkus-junit5` is required for testing, as it provides the `@QuarkusTest` annotation that controls the testing framework. -`rest-assured` is not required but is a convenient way to test HTTP endpoints, we also provide integration that automatically -sets the correct URL so no configuration is required. +`rest-assured` is not required but is a convenient way to test HTTP endpoints, we also provide integration that automatically sets the correct URL so no configuration is required. Because we are using JUnit 5, the version of the https://maven.apache.org/surefire/maven-surefire-plugin/[Surefire Maven Plugin] must be set, as the default version does not support Junit 5: @@ -100,8 +100,7 @@ must be set, as the default version does not support Junit 5: ---- -We also set the `java.util.logging.manager` system property to make sure tests will use the correct logmanager and `maven.home` to ensure that custom configuration -from `${maven.home}/conf/settings.xml` is applied (if any). +We also set the `java.util.logging.manager` system property to make sure tests will use the correct logmanager and `maven.home` to ensure that custom configuration from `${maven.home}/conf/settings.xml` is applied (if any). The project should also contain a simple test: @@ -143,28 +142,29 @@ public class GreetingResourceTest { } ---- -This test uses HTTP to directly test our REST endpoint. When the test is run the application will be started before -the test is run. +This test uses HTTP to directly test our REST endpoint. +When the test is run the application will be started before the test is run. === Controlling the test port -While Quarkus will listen on port `8080` by default, when running tests it defaults to `8081`. This allows you to run -tests while having the application running in parallel. +While Quarkus will listen on port `8080` by default, when running tests it defaults to `8081`. +This allows you to run tests while having the application running in parallel. [TIP] .Changing the test port ==== You can configure the ports used by tests by configuring `quarkus.http.test-port` for HTTP and `quarkus.http.test-ssl-port` for HTTPS in your `application.properties`: + [source,properties] ---- quarkus.http.test-port=8083 quarkus.http.test-ssl-port=8446 ---- + `0` will result in the use of a random port (assigned by the operating system). ==== -Quarkus also provides RestAssured integration that updates the default port used by RestAssured before the tests are run, -so no additional configuration should be required. +Quarkus also provides RestAssured integration that updates the default port used by RestAssured before the tests are run, so no additional configuration should be required. === Controlling HTTP interaction timeout @@ -178,13 +178,13 @@ quarkus.http.test-timeout=10s === Injecting a URI -It is also possible to directly inject the URL into the test which can make is easy to use a different client. This is -done via the `@TestHTTPResource` annotation. +It is also possible to directly inject the URL into the test which can make is easy to use a different client. +This is done via the `@TestHTTPResource` annotation. -Let's write a simple test that shows this off to load some static resources. First create a simple HTML file in +Let's write a simple test that shows this off to load some static resources. +First create a simple HTML file in `src/main/resources/META-INF/resources/index.html` : - [source,xml] ---- @@ -199,7 +199,6 @@ Let's write a simple test that shows this off to load some static resources. Fir We will create a simple test to ensure that this is being served correctly: - [source,java] ---- package org.acme.getting.started.testing; @@ -230,15 +229,16 @@ public class StaticContentTest { } } ---- + <1> This annotation allows you to directly inject the URL of the Quarkus instance, the value of the annotation will be the path component of the URL For now `@TestHTTPResource` allows you to inject `URI`, `URL` and `String` representations of the URL. == Testing a specific endpoint -Both RESTassured and `@TestHTTPResource` allow you to specify the endpoint class you are testing rather than hard coding -a path. This currently supports both Jakarta REST endpoints, Servlets and Reactive Routes. This makes it a lot easier to see exactly which endpoints -a given test is testing. +Both RESTassured and `@TestHTTPResource` allow you to specify the endpoint class you are testing rather than hard coding a path. +This currently supports both Jakarta REST endpoints, Servlets and Reactive Routes. +This makes it a lot easier to see exactly which endpoints a given test is testing. For the purposes of these examples I am going to assume we have an endpoint that looks like the following: @@ -255,14 +255,14 @@ public class GreetingResource { } ---- -NOTE: This currently does not support the `@ApplicationPath()` annotation to set the Jakarta REST context path. Use the +NOTE: This currently does not support the `@ApplicationPath()` annotation to set the Jakarta REST context path. +Use the `quarkus.resteasy.path` config value instead if you want a custom context path. === TestHTTPResource -You can the use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation to specify the endpoint path, and the path -will be extracted from the provided endpoint. If you also specify a value for the `TestHTTPResource` endpoint it will -be appended to the end of the endpoint path. +You can the use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation to specify the endpoint path, and the path will be extracted from the provided endpoint. +If you also specify a value for the `TestHTTPResource` endpoint it will be appended to the end of the endpoint path. [source,java] ---- @@ -296,14 +296,14 @@ public class StaticContentTest { } } ---- -<1> Because `GreetingResource` is annotated with `@Path("/hello")` the injected URL -will end with `/hello`. + +<1> Because `GreetingResource` is annotated with `@Path("/hello")` the injected URL will end with `/hello`. === RESTassured -To control the RESTassured base path (i.e. the default path that serves as the root for every -request) you can use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation. This can -be applied at the class or method level. To test out greeting resource we would do: +To control the RESTassured base path (i.e. the default path that serves as the root for every request) you can use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation. +This can be applied at the class or method level. +To test out greeting resource we would do: [source,java] ---- @@ -331,18 +331,16 @@ public class GreetingResourceTest { } } ---- + <1> This tells RESTAssured to prefix all requests with `/hello`. <2> Note we don't need to specify a path here, as `/hello` is the default for this test == Injection into tests -So far we have only covered integration style tests that test the app via HTTP endpoints, but what if we want to do unit -testing and test our beans directly? - -Quarkus supports this by allowing you to inject CDI beans into your tests via the `@Inject` annotation (in fact, tests in -Quarkus are full CDI beans, so you can use all CDI functionality). Let's create a simple test that tests the greeting -service directly without using HTTP: +So far we have only covered integration style tests that test the app via HTTP endpoints, but what if we want to do unit testing and test our beans directly? +Quarkus supports this by allowing you to inject CDI beans into your tests via the `@Inject` annotation (in fact, tests in Quarkus are full CDI beans, so you can use all CDI functionality). +Let's create a simple test that tests the greeting service directly without using HTTP: [source,java] ---- @@ -367,15 +365,17 @@ public class GreetingServiceTest { } } ---- + <1> The `GreetingService` bean will be injected into the test == Applying Interceptors to Tests -As mentioned above Quarkus tests are actually full CDI beans, and as such you can apply CDI interceptors as you would -normally. As an example, if you want a test method to run within the context of a transaction you can simply apply the +As mentioned above Quarkus tests are actually full CDI beans, and as such you can apply CDI interceptors as you would normally. +As an example, if you want a test method to run within the context of a transaction you can simply apply the `@Transactional` annotation to the method and the transaction interceptor will handle it. -In addition to this you can also create your own test stereotypes. For example, we could create a `@TransactionalQuarkusTest` +In addition to this you can also create your own test stereotypes. +For example, we could create a `@TransactionalQuarkusTest` as follows: [source,java] @@ -392,7 +392,6 @@ public @interface TransactionalQuarkusTest { If we then apply this annotation to a test class it will act as if we had applied both the `@QuarkusTest` and `@Transactional` annotations, e.g.: - [source,java] ---- @TransactionalQuarkusTest @@ -411,10 +410,9 @@ public class TestStereotypeTestCase { == Tests and Transactions -You can use the standard Quarkus `@Transactional` annotation on tests, but this means that the changes your -test makes to the database will be persistent. If you want any changes made to be rolled back at the end of -the test you can use the `io.quarkus.test.TestTransaction` annotation. This will run the test method in a -transaction, but roll it back once the test method is complete to revert any database changes. +You can use the standard Quarkus `@Transactional` annotation on tests, but this means that the changes your test makes to the database will be persistent. +If you want any changes made to be rolled back at the end of the test you can use the `io.quarkus.test.TestTransaction` annotation. +This will run the test method in a transaction, but roll it back once the test method is complete to revert any database changes. == Enrichment via QuarkusTest*Callback @@ -432,6 +430,7 @@ Optionally, you can enable these callbacks also for the `@QuarkusIntegrationTest Such a callback implementation has to be registered as a "service provider" as defined by `java.util.ServiceLoader`. E.g. the following sample callback: + [source,java] ---- package org.acme.getting.started.testing; @@ -447,7 +446,9 @@ public class MyQuarkusTestBeforeEachCallback implements QuarkusTestBeforeEachCal } } ---- + has to be registered via `src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback` as follows: + [source] ---- org.acme.getting.started.testing.MyQuarkusTestBeforeEachCallback @@ -455,19 +456,18 @@ org.acme.getting.started.testing.MyQuarkusTestBeforeEachCallback TIP: It is possible to read annotations from the test class or method to control what the callback shall be doing. -WARNING: While it is possible to use JUnit Jupiter callback interfaces like `BeforeEachCallback`, you might run into classloading issues because Quarkus has - to run tests in a custom classloader which JUnit is not aware of. +WARNING: While it is possible to use JUnit Jupiter callback interfaces like `BeforeEachCallback`, you might run into classloading issues because Quarkus has to run tests in a custom classloader which JUnit is not aware of. [[testing_different_profiles]] == Testing Different Profiles -So far in all our examples we only start Quarkus once for all tests. Before the first test is run Quarkus will boot, -then all tests will run, then Quarkus will shut down at the end. This makes for a very fast testing experience however -it is a bit limited as you can't test different configurations. +So far in all our examples we only start Quarkus once for all tests. +Before the first test is run Quarkus will boot, then all tests will run, then Quarkus will shut down at the end. +This makes for a very fast testing experience however it is a bit limited as you can't test different configurations. -To get around this Quarkus supports the idea of a test profile. If a test has a different profile to the previously -run test then Quarkus will be shut down and started with the new profile before running the tests. This is obviously -a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a great deal of flexibility. +To get around this Quarkus supports the idea of a test profile. +If a test has a different profile to the previously run test then Quarkus will be shut down and started with the new profile before running the tests. +This is obviously a bit slower, as it adds a shutdown/startup cycle to the test time, but gives a great deal of flexibility. To reduce the amount of times Quarkus needs to restart, `io.quarkus.test.junit.util.QuarkusTestProfileAwareClassOrderer` is registered as a global `ClassOrderer` as described in the @@ -476,6 +476,7 @@ The behavior of this `ClassOrderer` is configurable via `junit-platform.properti It can also be disabled entirely by setting another `ClassOrderer` that is provided by JUnit 5 or even your own custom one. + Please note that as of JUnit 5.8.2 link:https://github.com/junit-team/junit5/issues/2794[only a single `junit-platform.properties` is picked up and a warning is logged if more than one is found]. If you encounter such warnings, you can get rid of them by removing the Quarkus-supplied `junit-platform.properties` from the classpath via an exclusion: + [source,xml] ---- @@ -610,25 +611,25 @@ public class MockGreetingProfile implements QuarkusTestProfile { <1> } } ---- + <1> All these methods have default implementations so just override the ones you need to override. <2> If a test profile implementation declares a CDI bean (via producer method/field or nested static class) then this bean is only taken into account if the test profile is used, i.e. it's ignored for any other test profile. Now we have defined our profile we need to include it on our test class. We do this by annotating the test class with `@TestProfile(MockGreetingProfile.class)`. -All the test profile configuration is stored in a single class, which makes it easy to tell if the previous test ran with the -same configuration. +All the test profile configuration is stored in a single class, which makes it easy to tell if the previous test ran with the same configuration. === Running specific tests Quarkus provides the ability to limit test execution to tests with specific `@TestProfile` annotations. This works by leveraging the `tags` method of `QuarkusTestProfile` in conjunction with the `quarkus.test.profile.tags` system property. -Essentially, any `QuarkusTestProfile` with at least one matching tag matching the value of `quarkus.test.profile.tags` will be considered active -and all the tests annotated with `@TestProfile` of active profiles, will be run while the rest will be skipped. +Essentially, any `QuarkusTestProfile` with at least one matching tag matching the value of `quarkus.test.profile.tags` will be considered active and all the tests annotated with `@TestProfile` of active profiles, will be run while the rest will be skipped. This is best shown in the following example. First let's define a few `QuarkusTestProfile` implementations like so: + [source,java] ---- public class Profiles { @@ -711,16 +712,14 @@ Let's consider the following scenarios: * `quarkus.test.profile.tags` is not set: All tests will be executed. * `quarkus.test.profile.tags=foo`: In this case none of tests will be executed because none of the tags defined on the `QuarkusTestProfile` implementations match the value of `quarkus.test.profile.tags`. Note that `NoQuarkusProfileTest` is not executed either because it is not annotated with `@TestProfile`. -* `quarkus.test.profile.tags=test1`: In this case `SingleTagTest` and `MultipleTagsTest` will be run because the tags on their respective `QuarkusTestProfile` implementations -match the value of `quarkus.test.profile.tags`. +* `quarkus.test.profile.tags=test1`: In this case `SingleTagTest` and `MultipleTagsTest` will be run because the tags on their respective `QuarkusTestProfile` implementations match the value of `quarkus.test.profile.tags`. * `quarkus.test.profile.tags=test1,test3`: This case results in the same tests being executed as the previous case. -* `quarkus.test.profile.tags=test2,test3`: In this case only `MultipleTagsTest` will be run because `MultipleTagsTest` is the only `QuarkusTestProfile` implementation whose `tags` method -matches the value of `quarkus.test.profile.tags`. +* `quarkus.test.profile.tags=test2,test3`: In this case only `MultipleTagsTest` will be run because `MultipleTagsTest` is the only `QuarkusTestProfile` implementation whose `tags` method matches the value of `quarkus.test.profile.tags`. == Mock Support -Quarkus supports the use of mock objects using two different approaches. You can either use CDI alternatives to -mock out a bean for all test classes, or use `QuarkusMock` to mock out beans on a per test basis. +Quarkus supports the use of mock objects using two different approaches. +You can either use CDI alternatives to mock out a bean for all test classes, or use `QuarkusMock` to mock out beans on a per test basis. === CDI `@Alternative` mechanism. @@ -755,23 +754,20 @@ public class MockExternalService extends ExternalService { } } ---- + <1> Overrides the `@Dependent` scope declared on the `@Mock` stereotype. -It is important that the alternative be present in the `src/test/java` directory rather than `src/main/java`, as otherwise -it will take effect all the time, not just when testing. +It is important that the alternative be present in the `src/test/java` directory rather than `src/main/java`, as otherwise it will take effect all the time, not just when testing. -Note that at present this approach does not work with native image testing, as this would require the test alternatives -to be baked into the native image. +Note that at present this approach does not work with native image testing, as this would require the test alternatives to be baked into the native image. [[quarkus_mock]] === Mocking using QuarkusMock -The `io.quarkus.test.junit.QuarkusMock` class can be used to temporarily mock out any normal scoped -bean. If you use this method in a `@BeforeAll` method the mock will take effect for all tests on the current class, -while if you use this in a test method the mock will only take effect for the duration of the current test. +The `io.quarkus.test.junit.QuarkusMock` class can be used to temporarily mock out any normal scoped bean. +If you use this method in a `@BeforeAll` method the mock will take effect for all tests on the current class, while if you use this in a test method the mock will only take effect for the duration of the current test. -This method can be used for any normal scoped CDI bean (e.g. `@ApplicationScoped`, `@RequestScoped` etc, basically -every scope except `@Singleton` and `@Dependent`). +This method can be used for any normal scoped CDI bean (e.g. `@ApplicationScoped`, `@RequestScoped` etc, basically every scope except `@Singleton` and `@Dependent`). An example usage could look like: @@ -830,11 +826,11 @@ public class MockTestCase { } } ---- + <1> As the injected instance is not available here we use `installMockForType`, this mock is used for both test methods <2> We use `installMockForInstance` to replace the injected bean, this takes effect for the duration of the test method. -Note that there is no dependency on Mockito, you can use any mocking library you like, or even manually override the -objects to provide the behaviour you require. +Note that there is no dependency on Mockito, you can use any mocking library you like, or even manually override the objects to provide the behaviour you require. NOTE: Using `@Inject` will get you a CDI proxy to the mock instance you install, which is not suitable for passing to methods such as `Mockito.verify` which want the mock instance itself. @@ -847,6 +843,7 @@ Building on the features provided by `QuarkusMock`, Quarkus also allows users to [IMPORTANT] ==== This functionality is available with the `@io.quarkus.test.InjectMock` annotation **only if** the `quarkus-junit5-mockito` dependency is present: + [source,xml] ---- @@ -906,13 +903,14 @@ public class MockTestCase { } } ---- + <1> `@InjectMock` results in a Mockito mock being created, which is then available in test methods of the test class (other test classes are *not* affected by this) <2> The `mockableBean1` is configured here for every test method of the class <3> Since the `mockableBean2` mock has not been configured, it will return the default Mockito response. <4> In this test the `mockableBean2` is configured, so it returns the configured response. -Although the test above is good for showing the capabilities of `@InjectMock`, it is not a good representation of a real test. In a real test -we would most likely configure a mock, but then test a bean that uses the mocked bean. +Although the test above is good for showing the capabilities of `@InjectMock`, it is not a good representation of a real test. +In a real test we would most likely configure a mock, but then test a bean that uses the mocked bean. Here is an example: [source,java] @@ -957,6 +955,7 @@ public class MockGreetingServiceTest { } } ---- + <1> Since we configured `greetingService` as a mock, the `GreetingResource` which uses the `GreetingService` bean, we get the mocked response instead of the response of the regular `GreetingService` bean By default, the `@InjectMock` annotation can be used for any normal CDI scoped bean (e.g. `@ApplicationScoped`, `@RequestScoped`). @@ -970,7 +969,8 @@ This is considered an advanced option and should only be performed if you fully Building on the features provided by `InjectMock`, Quarkus also allows users to effortlessly take advantage of link:https://site.mockito.org/[Mockito] for spying on the beans supported by `QuarkusMock`. This functionality is available via the `@io.quarkus.test.junit.mockito.InjectSpy` annotation which is available in the `quarkus-junit5-mockito` dependency. -Sometimes when testing you only need to verify that a certain logical path was taken, or you only need to stub out a single method's response while still executing the rest of the methods on the Spied clone. Please see link:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#13[Mockito documentation - Spying on real objects] for more details on Spy partial mocks. +Sometimes when testing you only need to verify that a certain logical path was taken, or you only need to stub out a single method's response while still executing the rest of the methods on the Spied clone. +Please see link:https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#13[Mockito documentation - Spying on real objects] for more details on Spy partial mocks. In either of those situations a Spy of the object is preferable. Using `@InjectSpy`, the previous example could be written as follows: @@ -1027,8 +1027,12 @@ public class SpyGreetingServiceTest { } } ---- + <1> Instead of overriding the value, we just want to ensure that the greet method on our `GreetingService` was called by this test. -<2> Here we are telling the Spy to return "hi" instead of "hello". When the `GreetingResource` requests the greeting from `GreetingService` we get the mocked response instead of the response of the regular `GreetingService` bean. Sometimes it's impossible or impractical to use `when(Object)` for stubbing spies. Therefore when using spies please consider `doReturn|Answer|Throw()` family of methods for stubbing. +<2> Here we are telling the Spy to return "hi" instead of "hello". +When the `GreetingResource` requests the greeting from `GreetingService` we get the mocked response instead of the response of the regular `GreetingService` bean. +Sometimes it's impossible or impractical to use `when(Object)` for stubbing spies. +Therefore when using spies please consider `doReturn|Answer|Throw()` family of methods for stubbing. <3> We are verifying that we get the mocked response from the Spy. ==== Using `@InjectMock` with `@RestClient` @@ -1073,6 +1077,7 @@ public class GreetingResourceTest { } ---- + <1> Indicate that this injection point is meant to use an instance of `RestClient`. === Mocking with Panache @@ -1086,25 +1091,26 @@ If you are using Quarkus Security, check out the xref:security-testing.adoc[Test [[quarkus-test-resource]] == Starting services before the Quarkus application starts -A very common need is to start some services on which your Quarkus application depends, before the Quarkus application starts for testing. To address this need, Quarkus provides `@io.quarkus.test.common.QuarkusTestResource` and `io.quarkus.test.common.QuarkusTestResourceLifecycleManager`. +A very common need is to start some services on which your Quarkus application depends, before the Quarkus application starts for testing. +To address this need, Quarkus provides `@io.quarkus.test.common.QuarkusTestResource` and `io.quarkus.test.common.QuarkusTestResourceLifecycleManager`. By simply annotating any test in the test suite with `@QuarkusTestResource`, Quarkus will run the corresponding `QuarkusTestResourceLifecycleManager` before any tests are run. A test suite is also free to utilize multiple `@QuarkusTestResource` annotations, in which case all the corresponding `QuarkusTestResourceLifecycleManager` objects will be run before the tests. -NOTE: Test resources are global, even if they are defined on a test class or custom profile, which means they will all be activated for all tests, even though we do -remove duplicates. If you want to only enable a test resource on a single test class or test profile, you can use `@QuarkusTestResource(restrictToAnnotatedClass = true)`. - -NOTE: When using multiple test resources, they can be started concurrently. For that you need to set `@QuarkusTestResource(parallel = true)`. +NOTE: Test resources are global, even if they are defined on a test class or custom profile, which means they will all be activated for all tests, even though we do remove duplicates. +If you want to only enable a test resource on a single test class or test profile, you can use `@QuarkusTestResource(restrictToAnnotatedClass = true)`. -Quarkus provides a few implementations of `QuarkusTestResourceLifecycleManager` out of the box (see `io.quarkus.test.h2.H2DatabaseTestResource` which starts an H2 database, or `io.quarkus.test.kubernetes.client.KubernetesServerTestResource` which starts a mock Kubernetes API server), -but it is common to create custom implementations to address specific application needs. -Common cases include starting docker containers using https://www.testcontainers.org/[Testcontainers] (an example of which can be found https://github.com/quarkusio/quarkus/blob/main/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java[here]), -or starting a mock HTTP server using https://wiremock.org/[Wiremock] (an example of which can be found https://github.com/geoand/quarkus-test-demo/blob/main/src/test/java/org/acme/getting/started/country/WiremockCountries.java[here]). +NOTE: When using multiple test resources, they can be started concurrently. +For that you need to set `@QuarkusTestResource(parallel = true)`. -NOTE: As `QuarkusTestResourceLifecycleManager` is not a CDI Bean, classes that implement it can't have fields injected with `@Inject`. You can use `String propertyName = ConfigProvider.getConfig().getValue("quarkus.my-config-group.myconfig", String.class);` +Quarkus provides a few implementations of `QuarkusTestResourceLifecycleManager` out of the box (see `io.quarkus.test.h2.H2DatabaseTestResource` which starts an H2 database, or `io.quarkus.test.kubernetes.client.KubernetesServerTestResource` which starts a mock Kubernetes API server), but it is common to create custom implementations to address specific application needs. +Common cases include starting docker containers using https://www.testcontainers.org/[Testcontainers] (an example of which can be found https://github.com/quarkusio/quarkus/blob/main/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java[here]), or starting a mock HTTP server using https://wiremock.org/[Wiremock] (an example of which can be found https://github.com/geoand/quarkus-test-demo/blob/main/src/test/java/org/acme/getting/started/country/WiremockCountries.java[here]). +NOTE: As `QuarkusTestResourceLifecycleManager` is not a CDI Bean, classes that implement it can't have fields injected with `@Inject`. +You can use `String propertyName = ConfigProvider.getConfig().getValue("quarkus.my-config-group.myconfig", String.class);` === Altering the test class + When creating a custom `QuarkusTestResourceLifecycleManager` that needs to inject something into the test class, the `inject` methods can be used. If for example you have a test like the following: @@ -1157,16 +1163,16 @@ public class MyWireMockResource implements QuarkusTestResourceLifecycleManager { } ---- -IMPORTANT: It is worth mentioning that this injection into the test class is not under the control of CDI and happens after CDI has performed -any necessary injections into the test class. +IMPORTANT: It is worth mentioning that this injection into the test class is not under the control of CDI and happens after CDI has performed any necessary injections into the test class. === Annotation-based test resources -It is possible to write test resources that are enabled and configured using annotations. This is enabled by placing the `@QuarkusTestResource` +It is possible to write test resources that are enabled and configured using annotations. +This is enabled by placing the `@QuarkusTestResource` on an annotation which will be used to enable and configure the test resource. -For example, this defines the `@WithKubernetesTestServer` annotation, which you can use on your tests to activate the `KubernetesServerTestResource`, -but only for the annotated test class. You can also place them on your `QuarkusTestProfile` test profiles. +For example, this defines the `@WithKubernetesTestServer` annotation, which you can use on your tests to activate the `KubernetesServerTestResource`, but only for the annotated test class. +You can also place them on your `QuarkusTestProfile` test profiles. [source,java] ---- @@ -1236,24 +1242,45 @@ public @interface WithRepeatableTestResource { } ---- +=== Usage of `@WithTestResources` + +While test resources provided by `@QuarkusTestResource` are available either globally or restricted to the annotated test class (`restrictToAnnotatedClass`), the annotation `@WithTestResources` allows to additionally group tests by test resources for execution. +`@WithTestResources` has a `scope` property that takes a `TestResourceScope` enum value: + +- `TestResourceScope.MATCHING_RESOURCES` (default) + +Quarkus will group tests with the same test resources and run them together. After a group has been executed, all test resources will be stopped, and the next group will be executed. +- `TestResourceScope.RESTRICTED_TO_CLASS` + +The test resource is available only for the annotated test class and will be stopped after the test class has been executed. This is equivalent to using @QuarkusTestResource with restrictToAnnotatedClass = true. +- `TestResourceScope.GLOBAL` + +The test resource is available globally. +This is equivalent to using `@QuarkusTestResource` with `restrictToAnnotatedClass = false`. +NOTE: `@QuarkusTestResource` is merely a convenient extension of `@WithTestResources` for the use of global test resources + +BELOW TO EDIT: + +I don't know the behaviour when different scopes are mixed 🤷‍♂️Example: +```java +@WithTestResources(value = TestResourceA.class, scope = TestResourceScope.MATCHING_RESOURCES) +@WithTestResources(value = TestResourceB.class, scope = TestResourceScope.RESTRICTED_TO_CLASS) +@WithTestResources(value = TestResourceC.class, scope = TestResourceScope.GLOBAL) +class TestClass(){} +``` +Also maybe add a use case example for why the different scopes are useful, as we currently only use GLOBAL because of time reason. == Hang Detection -`@QuarkusTest` has support for hang detection to help diagnose any unexpected hangs. If no progress is made for a specified -time (i.e. no JUnit callbacks are invoked) then Quarkus will print a stack trace to the console to help diagnose the hang. +`@QuarkusTest` has support for hang detection to help diagnose any unexpected hangs. +If no progress is made for a specified time (i.e. no JUnit callbacks are invoked) then Quarkus will print a stack trace to the console to help diagnose the hang. The default value for this timeout is 10 minutes. -No further action will be taken, and the tests will continue as normal (generally until CI times out), however the printed -stack traces should help diagnose why the build has failed. You can control this timeout with the -`quarkus.test.hang-detection-timeout` system property (you can also set this in application.properties, but this won't -be read until Quarkus has started, so the timeout for Quarkus start will be the default of 10 minutes). +No further action will be taken, and the tests will continue as normal (generally until CI times out), however the printed stack traces should help diagnose why the build has failed. +You can control this timeout with the +`quarkus.test.hang-detection-timeout` system property (you can also set this in application.properties, but this won't be read until Quarkus has started, so the timeout for Quarkus start will be the default of 10 minutes). == Native Executable Testing -It is also possible to test native executables using `@QuarkusIntegrationTest`. This supports all the features mentioned in this -guide except injecting into tests (and the native executable runs in a separate non-JVM process this is not really possible). - +It is also possible to test native executables using `@QuarkusIntegrationTest`. +This supports all the features mentioned in this guide except injecting into tests (and the native executable runs in a separate non-JVM process this is not really possible). This is covered in the xref:building-native-image.adoc[Native Executable Guide]. @@ -1276,7 +1303,7 @@ These tests will **not** work if run in the same phase as `@QuarkusTest` as Quar The `pom.xml` file contains: -[source, xml] +[source,xml] ---- org.apache.maven.plugins @@ -1302,7 +1329,8 @@ The `pom.xml` file contains: This instructs the failsafe-maven-plugin to run integration-test. -Then, open the `src/test/java/org/acme/quickstart/GreetingResourceIT.java`. It contains: +Then, open the `src/test/java/org/acme/quickstart/GreetingResourceIT.java`. +It contains: [source,java] ---- @@ -1318,6 +1346,7 @@ public class GreetingResourceIT extends GreetingResourceTest { // <2> } ---- + <1> Use another test runner that starts the application from the native file before the tests. The executable is retrieved by the _Failsafe Maven Plugin_. <2> We extend our previous tests as a convenience, but you can also implement your tests. @@ -1331,15 +1360,16 @@ When the application is tested using `@QuarkusIntegrationTest` it is launched us [IMPORTANT] ==== -While adding test-specific configuration properties using `src/test/resources/application.properties` (note there's `test`, not `main`) -is possible for unit tests, it's not possible for integration tests. +While adding test-specific configuration properties using `src/test/resources/application.properties` (note there's `test`, not `main`) is possible for unit tests, it's not possible for integration tests. ==== === Launching containers -When `@QuarkusIntegrationTest` results in launching a container (because the application was built with `quarkus.container-image.build` set to `true`), the container is launched on a predictable container network. This facilitates writing integration tests that need to launch services to support the application. +When `@QuarkusIntegrationTest` results in launching a container (because the application was built with `quarkus.container-image.build` set to `true`), the container is launched on a predictable container network. +This facilitates writing integration tests that need to launch services to support the application. This means that `@QuarkusIntegrationTest` works out of the box with containers launched via xref:dev-services.adoc[Dev Services], but it also means that it enables using <> resources that launch additional containers. -This can be achieved by having your `QuarkusTestLifecycleManager` implement `io.quarkus.test.common.DevServicesContext.ContextAware`. A simple example could be the following: +This can be achieved by having your `QuarkusTestLifecycleManager` implement `io.quarkus.test.common.DevServicesContext.ContextAware`. +A simple example could be the following: The container running the resource to test against, for example PostgreSQL via Testcontainers, is assigned an IP address from the container's network. Use the container's "public" IP from its network and the "unmapped" port number to connect to the service. @@ -1418,7 +1448,8 @@ public class CustomResource implements QuarkusTestResourceLifecycleManager, DevS === Executing against a running application -`@QuarkusIntegrationTest` supports executing tests against an already running instance of the application. This can be achieved by setting the +`@QuarkusIntegrationTest` supports executing tests against an already running instance of the application. +This can be achieved by setting the `quarkus.http.test-host` system property when running the tests. An example use of this could be the following Maven command, that forces `@QuarkusIntegrationTest` to execute against that is accessible at `http://1.2.3.4:4321`: @@ -1433,11 +1464,9 @@ To test against a running instance that only accepts SSL/TLS connection (example == Mixing `@QuarkusTest` with other type of tests Mixing tests annotated with `@QuarkusTest` with tests annotated with either `@QuarkusDevModeTest`, `@QuarkusProdModeTest` or `@QuarkusUnitTest` -is not allowed in a single execution run (in a single Maven Surefire Plugin execution, for instance), -while the latter three can coexist. +is not allowed in a single execution run (in a single Maven Surefire Plugin execution, for instance), while the latter three can coexist. -The reason of this restriction is that `@QuarkusTest` starts a Quarkus server for the whole lifetime of the tests execution run, -thus preventing the other tests to start their own Quarkus server. +The reason of this restriction is that `@QuarkusTest` starts a Quarkus server for the whole lifetime of the tests execution run, thus preventing the other tests to start their own Quarkus server. To alleviate this restriction, the `@QuarkusTest` annotation defines a JUnit 5 `@Tag`: `io.quarkus.test.junit.QuarkusTest`. You can use this tag to isolate the `@QuarkusTest` test in a specific execution run, for example with the Maven Surefire Plugin: @@ -1477,13 +1506,13 @@ You can use this tag to isolate the `@QuarkusTest` test in a specific execution [NOTE] -- -Currently `@QuarkusTest` and `@QuarkusIntegrationTest` should not be run in the same test run. +Currently `@QuarkusTest` and `@QuarkusIntegrationTest` should not be run in the same test run. For Maven, this means that the former should be run by the surefire plugin while the latter should be run by the failsafe plugin. For Gradle, this means the two types of tests should belong to different source sets. -.Source set configuration example +.Source set configuration example [%collapsible] ==== [source,kotlin,subs=attributes+] @@ -1559,7 +1588,8 @@ idea.module { [[test-from-ide]] == Running `@QuarkusTest` from an IDE -Most IDEs offer the possibility to run a selected class as a JUnit test directly. For this you should set a few properties in the settings of your chosen IDE: +Most IDEs offer the possibility to run a selected class as a JUnit test directly. +For this you should set a few properties in the settings of your chosen IDE: * `java.util.logging.manager` (see xref:logging.adoc[Logging Guide]) @@ -1580,7 +1610,8 @@ Use this JRE definition as your Quarkus project targeted runtime and the workaro === VSCode "run with" configuration The `settings.json` placed in the root of your project directory or in the workspace will need the following workaround in your test configuration: -[source, json] + +[source,json] ---- "java.test.config": [ { @@ -1598,22 +1629,20 @@ Nothing needed in IntelliJ IDEA because the IDE will pick the `systemPropertyVar == Testing Dev Services -By default, tests should just work with xref:dev-services.adoc[Dev Services], however from some use cases you may need access to -the automatically configured properties in your tests. +By default, tests should just work with xref:dev-services.adoc[Dev Services], however from some use cases you may need access to the automatically configured properties in your tests. You can do this with `io.quarkus.test.common.DevServicesContext`, which can be injected directly into any `@QuarkusTest` -or `@QuarkusIntegrationTest`. All you need to do is define a field of type `DevServicesContext` and it will be automatically -injected. Using this you can retrieve any properties that have been set. Generally this is used to directly connect to a -resource from the test itself, e.g. to connect to kafka to send messages to the application under test. +or `@QuarkusIntegrationTest`. +All you need to do is define a field of type `DevServicesContext` and it will be automatically injected. +Using this you can retrieve any properties that have been set. +Generally this is used to directly connect to a resource from the test itself, e.g. to connect to kafka to send messages to the application under test. -Injection is also supported into objects that implement `io.quarkus.test.common.DevServicesContext.ContextAware`. If you -have a field that implements `io.quarkus.test.common.DevServicesContext.ContextAware` Quarkus will call the -`setIntegrationTestContext` method to pass the context into this object. This allows client logic to be encapsulated in -a utility class. - -`QuarkusTestResourceLifecycleManager` implementations can also implement `ContextAware` to get access to these properties, -which allows you to set up the resource before Quarkus starts (e.g. configure a KeyCloak instance, add data to a database etc). +Injection is also supported into objects that implement `io.quarkus.test.common.DevServicesContext.ContextAware`. +If you have a field that implements `io.quarkus.test.common.DevServicesContext.ContextAware` Quarkus will call the +`setIntegrationTestContext` method to pass the context into this object. +This allows client logic to be encapsulated in a utility class. +`QuarkusTestResourceLifecycleManager` implementations can also implement `ContextAware` to get access to these properties, which allows you to set up the resource before Quarkus starts (e.g. configure a KeyCloak instance, add data to a database etc). [NOTE] ==== @@ -1629,7 +1658,7 @@ This JUnit extension is available in the `quarkus-junit5-component` dependency. Let's have a component `Foo` - a CDI bean with two injection points. .`Foo` component -[source, java] +[source,java] ---- package org.acme; @@ -1650,6 +1679,7 @@ public class Foo { } } ---- + <1> `Foo` is an `@ApplicationScoped` CDI bean. <2> `Foo` depends on `Charlie` which declares a method `ping()`. <3> `Foo` depends on the config property `bar`. `@Inject` is not needed for this injection point because it also declares a CDI qualifier - this is a Quarkus-specific feature. @@ -1657,7 +1687,7 @@ public class Foo { Then a component test could look like: .Simple component test -[source, java] +[source,java] ---- import static org.junit.jupiter.api.Assertions.assertEquals; @@ -1685,10 +1715,15 @@ public class FooTest { } } ---- + <1> The `QuarkusComponentTest` annotation registers the JUnit extension. <2> Sets a configuration property for the test. -<3> The test injects the component under the test. The types of all fields annotated with `@Inject` are considered the component types under test. You can also specify additional component classes via `@QuarkusComponentTest#value()`. Furthermore, the static nested classes declared on the test class are components too. -<4> The test also injects a mock for `Charlie`. `Charlie` is an _unsatisfied_ dependency for which a synthetic `@Singleton` bean is registered automatically. The injected reference is an "unconfigured" Mockito mock. +<3> The test injects the component under the test. +The types of all fields annotated with `@Inject` are considered the component types under test. +You can also specify additional component classes via `@QuarkusComponentTest#value()`. +Furthermore, the static nested classes declared on the test class are components too. +<4> The test also injects a mock for `Charlie`. `Charlie` is an _unsatisfied_ dependency for which a synthetic `@Singleton` bean is registered automatically. +The injected reference is an "unconfigured" Mockito mock. <5> We can leverage the Mockito API in a test method to configure the behavior. You can find more examples and hints in the xref:testing-components.adoc[testing components reference guide].