Key Takeaways
- Quarkus is a full-stack, Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation. Although many testing techniques remain the same, Quarkus provides supporting technologies to ease test set up and execution.
- In this article, we will learn how to write clean integration tests for Quarkus applications. We will see how we can write simple and clean tests for the following scenarios: a mail client, security with RBAC, testing using containers, and rest clients.
- Quarkus has a Quarkus Test Security module that allow deterministic modification of the security context during the test phase.
- Testcontainers is a Java library that provides a way to start/stop Docker containers programmatically from the Java code. I supports out-of-the-box most used database containers, Kafka, Localstack, or WebDrivers to name a few.
- Service virtualization is a technique used to simulate the behavior of dependencies of a service. Although service virtualization is commonly associated with REST API-based services, the same concept can be applied to any other kind of dependencies such as databases, ESBs, and JMS.
Quarkus is a full-stack, Kubernetes-native Java framework made for Java virtual machines (JVMs) and native compilation, optimizing Java specifically for containers and enabling it to become an effective platform for serverless, cloud, and Kubernetes environments.
Instead of reinventing the wheel, Quarkus uses well-known enterprise-grade frameworks backed by standards/specifications and makes them compilable to a binary using Graal VM.
In this article, we will learn how to write clean integration tests for Quarkus applications. We will see how we can write simple and clean tests for the following scenarios:
- Mail Client
- Security with RBAC
- Testing using Containers
- Rest Clients
Let’s see how we can write tests.
The application
We’ll use the same application from part 1 of this article located here.
As a reminder, the application is a simple user registration service developed in Quarkus, and it’s composed of the following classes:
Mail Client
Let’s add a new requirement when a new user is registered. Suppose every time a new user is registered, the application should send an email to the user with the auto-generated password. The logic to implement this use case uses the quarkus-mailer
extension and it’s implemented in a CDI bean:
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
@ApplicationScoped
public class MailService {
@Inject
Mailer mailer;
public void sendEmail(final User user) {
mailer.send(
Mail.withText(user.email, "Your New Password",
String.format("New Password %s.", user.password))
);
}
}
Now, we need to write a test for this class. One of the approaches that might come to mind is to use Mockito as we’ve seen in the previous article, and it’s a fair point, but Quarkus offers a stubbed mail client which is injected automatically in your code basis in dev
and test
profiles. Since it’s stubbed, you can query it for getting the total number of messages sent, getting all messages sent by a user, or wiping them out.
Since this stubbed mail client is automatically used in the test
profile, we can inject an instance of io.quarkus.mailer.MockMailbox
in our test and use it in the assertions section. In the following snippet you see what a MailService
test looks like:
@QuarkusTest
public class MailServiceTest {
@Inject
MockMailbox mailbox;
@Inject
MailService mailService;
@BeforeEach
void init() {
mailbox.clear();
}
@Test
public void shouldSendAnEmail() {
User user = new User("Alex", "alex@example.com", "abcd");
mailService.sendEmail(user);
assertThat(mailbox.getMessagesSentTo("alex@example.com"))
.hasSize(1)
.extracting("subject")
.containsExactly("Your New Password");
}
}
MockMailbox
is the class where all emails are sent. No real mail server is used.MailService
is injected as it’s the business logic under test.- Before every test run, the mailbox is cleaned so tests are isolated.
mailbox.getMessagesSentTo("alex@example.com"
) returns a list of all the emails sent by thealex@example.com
account.
INFO: All messages are printed to the Quarkus terminal when MockMailbox
is enabled.
TIP: You can disable MockMailbox
injection in dev and test profiles by setting the quarkus.mailer.mock
configuration property to false
.
Security and RBAC
So far, we’ve been accessing the REST endpoints freely without any kind of auth mechanism. While this might work for some endpoints, some others (especially the ones related to administration tasks) need to be protected.
Quarkus Security integrates with the JavaEE security model (i.e., javax.annotation.security.RolesAllowed
annotation) and provides multiple authentication and authorization mechanisms to use. To cite a few: OpenId Connect, JWT, Basic Auth, OAuth 2, JDBC, or LDAP.
Let’s protect the findUserByUsername
endpoint to limit the access to the Admin role as this is something that a regular user should never do:
@GET
@RolesAllowed("Admin")
@Path("/{username}")
@Produces(MediaType.APPLICATION_JSON)
public Response findUserByUsername(@PathParam("username") String username) {
}
Write a white-box test
Now, it’s time to write a white-box test using RestAssured to validate we can find a user by its username. (We’ve already done this in part 1 of this article.) The test for this endpoint is shown in the following snippet:
@Test
@Order(2)
public void shouldFindAUserByUsername() {
given()
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.when().get("/{username}", "Alex")
.then()
.statusCode(200)
.body("username", is("Alex"))
.body("email", is("asotobu@example.com"))
.body("password", is("my-secret-password"));
- There is a test method executed before this test that inserts a user.
- RestAssured is used to call the endpoint and also validates the response.
But, what happens when we execute this logic? Exactly! It fails because the status code isn’t 200 OK
but 401 Unauthorized
as the endpoint is protected and we’ve not been authenticated in the system.
So at this point, we have two options—the first one is to authenticate in the system. This could be a good approach but:
- The logic to authenticate against the system might not be easy. Think, for example, in the case of OAuth2. To run the test we would need to have an Identity provider like Keycloak configured with some test data, and the test needs to execute some logic to authenticate against the identity provider following the security protocol.
- The test should run fast—it now runs much slower as there is an overhead in the preparation of the test.
- Any change to the authentication mechanism affects all tests and adds a requirement/prerequisite.
Obviously, we need to test if the real security mechanism works, but this can be implemented in specific security tests, and not in tests where we’re validating the business logic. For this reason, let’s explore a second option.
Test Security
Quarkus has a Quarkus Test Security module that lets you modify the security context during the test phase.
To use the Quarkus Test Security module, we need to add the quarkus-test-security dependency in our build tool script. For example, in Maven you should add the following section in pom.xml:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-security</artifactId>
<scope>test</scope>
</dependency>
This module provides the io.quarkus.test.security.TestSecurity
annotation to control the security context the test is run with. Basically, you can either bypass authorization so tests can access secured endpoints without needing to be authenticated, and/or you can specify the user and roles you want the tests to use.
Let’s rewrite the previous test with authorization disabled.
@Test
@Order(2)
@TestSecurity(authorizationEnabled = false)
public void shouldFindAUserByUsername() {
...
}
- The test can access secured endpoints without needing to authenticate.
If we now rerun the test, the test succeeds as security is disabled for this specific test.
We can also use the same annotation to configure the current user/roles the test will run as:
@Test
@Order(2)
@TestSecurity(user = "john", roles = "Admin")
public void shouldFindAUserByUsername() {
...
}
If we run the test again, it passes because the user and the role match the security constraints. Change the roles
attribute from Admin
to Admin2
, and the test will fail because of a security issue.
Callbacks
Quarkus has an extension mechanism to enrich all your @QuarkusTest
classes by implementing the following callback interfaces:
io.quarkus.test.junit.callback.QuarkusTestBeforeClassCallback
executes the logic before any test class execution.io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
executes the logic before each test method execution.io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
executes the logic before each test method execution.
Let’s create a callback that is executed before any test method, printing the current test method and all annotations placed at the class level.
public class MyQuarkusTestBeforeEachCallback implements QuarkusTestBeforeEachCallback {
@Override
public void beforeEach(QuarkusTestMethodContext context) {
System.out.println("Executing " + context.getTestMethod());
Annotation[] annotations = context.getTestInstance().getClass().getAnnotations();
Arrays.stream(annotations)
.forEach(System.out::println);
}
}
- We can get the current test method using the
context
object. - The other element we can get is the current test instance. With the instance object, we can inject attributes to the test instance, read values, or read static meta-information like annotations.
Quarkus test callbacks need to be registered as a Java service provider. Create the following file:
src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
with the following content:
org.acme.MyQuarkusTestBeforeEachCallback
Then for every test run, org.acme.MyQuarkusTestBeforeEachCallback
is executed.
IMPORTANT: While it’s possible to use JUnit Jupiter callback interfaces, you might run into classloading issues because Quarkus has to run tests in a custom classloader which JUnit isn’t aware of.
Callbacks are a nice way to execute some logic before/after each test in a reusable way. A good use case for using callbacks could be the encapsulation of the authentication logic for your end-to-end tests. Instead of repeating the authentication logic in each test class, we could just delegate it to a callback.
import org.acme.api.OpenIdAuthentication;
public class OpenIdAuthenticationTestBeforeClassCallback implements QuarkusTestBeforeClassCallback {
@Override
public void beforeClass(Class<?> testClass) {
OpenIdAuthentication openIdAuth = testClass.getAnnotation(OpenIdAuthentication.class);
if (openIdAuth != null) {
String accessToken = getToken(openIdAuth);
final RequestSpecBuilder requestSpec = new RequestSpecBuilder();
requestSpec.addHeader("Authorization", "Bearer " + accessToken);
RestAssured.requestSpecification = requestSpec.build();
}
}
}
And then you only need to annotate tests with OpenIdAuthentication
.
@OpenIdAuthentication(username = "Ada", password = "Alexandra")
@QuarkusTest
public class RegeneratePasswordTest {
}
TIP: Callbacks aren’t CDI-aware so you cannot inject any CDI bean in the class. If you need to do it you can always rely on programmatic lookup via the io.quarkus.arc.Arc.container()
method.
With callbacks, we can implement reusable test logic that is executed before or after tests, but callbacks don’t cover all possible use cases we might find when developing tests. One of these common needs is to execute some logic before the Quarkus application starts, reconfigure Quarkus with specific configuration properties, and execute some logic before the Quarkus application stops. For example, starting a Docker Container using Testcontainers (https://www.testcontainers.org/) project, use the container during test execution, and at the end stop it.
So let’s see how to achieve this in Quarkus tests.
Quarkus Test Resource
Quarkus has a mechanism to execute some logic before the application is up and when it’s stopped as well as reconfigure the application with new values during test execution.
We only need to create a class implementing io.quarkus.test.common.QuarkusTestResourceLifecycleManager
and annotating one test of the test suite with @QuarkusTestResource
.
If multiple Quarkus Test Resources are defined, @QuarkusTestResource
has the parallel
attribute to start them concurrently.
Testcontainers
Testcontainers is a Java library that provides a way to start/stop Docker containers programmatically from the Java code, it supports out-of-the-box most used database containers, Kafka, Localstack, or WebDrivers to name a few.
To use the Testcontainers, we need to add the testcontainers dependencies in our build tool script. For example in Maven you should add the following section in pom.xml
:
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.15.1</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mariadb</artifactId>
<version>1.15.1</version>
</dependency>
Persistence Integration Tests
Let’s write a persistence integration test but instead of using an embedded in-memory database as we did in part 1, we use a Dockerized MariaDB database.
The first thing to do is to create a class implementing the QuarkusTestResourceLifecycleManager
interface. This class starts/stops a Dockerized MariaDB instance and configures the Quarkus data source to use it.
import org.testcontainers.containers.MariaDBContainer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
public class MariaDBResource implements QuarkusTestResourceLifecycleManager {
static MariaDBContainer<?> db = new MariaDBContainer<>("mariadb:10.3.6")
.withDatabaseName("mydb")
.withUsername("developer")
.withPassword("developer");
@Override
public Map<String, String> start() {
db.start();
final Map<String, String> conf = new HashMap<>();
conf.put("%test.quarkus.datasource.jdbc.url", db.getJdbcUrl());
conf.put("%test.quarkus.datasource.username", "developer");
conf.put("%test.quarkus.datasource.password", "developer");
conf.put("%test.quarkus.datasource.db-kind", "mariadb");
return conf;
}
@Override
public void stop() {
db.stop();
}
}
MariaDBContainer
class encapsulates all the logic to deal with the lifecycle of MariaDB containers.- A
Map
is returned with the new data source configuration used by the Quarkus application.
The second step is to annotate one of our tests with io.quarkus.test.common.QuarkusTestResource
annotation. It’s important to notice that although only one class is annotated, the logic is executed only once before the application is started hence the logic is only executed once for the whole test suite.
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@TestHTTPEndpoint(RegistrationResource.class)
@QuarkusTestResource(MariaDBResource.class)
public class RegistrationResourceTest {
@TestHTTPResource
@TestHTTPEndpoint(RegistrationResource.class)
URL url;
@InjectMock
PasswordGenerator passwordGenerator;
@Test
@Order(1)
public void shouldRegisterAUser() {
Mockito.when(passwordGenerator.generate()).thenReturn("my-secret-password");
final User user = new User();
user.username = "Alex";
user.email = "asotobu@example.com";
given()
.body(user)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.when().post()
.then()
.statusCode(Status.CREATED.getStatusCode())
.header("location", url + "/1");
}
}
Implementation of QuarkusTestResourceLifecycleManager
is set on QuarkusTestResource
annotation.
Jan 18, 2021 5:06:25 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.MariaDB103Dialect
2021-01-18 17:06:14,283 INFO [org.tes.doc.DockerClientProviderStrategy] (main) Loaded org.testcontainers.dockerclient.UnixSocketClientProviderStrategy from ~/.testcontainers.properties, will try it first
2021-01-18 17:06:15,043 INFO [org.tes.doc.DockerClientProviderStrategy] (main) Found Docker environment with local Unix socket (unix:///var/run/docker.sock)
2021-01-18 17:06:15,044 INFO [org.tes.DockerClientFactory] (main) Docker host IP address is localhost
2021-01-18 17:06:15,085 INFO [org.tes.DockerClientFactory] (main) Connected to docker:
Server Version: 19.03.8
API Version: 1.40
Operating System: Docker Desktop
Total Memory: 7964 MB
2021-01-18 17:06:15,088 INFO [org.tes.uti.ImageNameSubstitutor] (main) Image name substitution will be performed by: DefaultImageNameSubstitutor (composite of 'ConfigurationFileImageNameSubstitutor' and 'PrefixingImageNameSubstitutor')
2021-01-18 17:06:15,841 INFO [org.tes.DockerClientFactory] (main) Ryuk started - will monitor and terminate Testcontainers containers on JVM exit
2021-01-18 17:06:15,841 INFO [org.tes.DockerClientFactory] (main) Checking the system...
2021-01-18 17:06:15,842 INFO [org.tes.DockerClientFactory] (main) ✔︎ Docker server version should be at least 1.6.0
2021-01-18 17:06:15,955 INFO [org.tes.DockerClientFactory] (main) ✔︎ Docker environment should have more than 2GB free disk space
2021-01-18 17:06:16,125 INFO [ .3.6]] (main) Creating container for image: mariadb:10.3.6
2021-01-18 17:06:16,282 INFO [ .3.6]] (main) Starting container with ID: e7206ee3b7f529526e0207321df3ea67487d8ab0e652bba9d6bc0200bc9bd61a
2021-01-18 17:06:16,543 INFO [ .3.6]] (main) Container mariadb:10.3.6 is starting: e7206ee3b7f529526e0207321df3ea67487d8ab0e652bba9d6bc0200bc9bd61a
2021-01-18 17:06:16,555 INFO [ .3.6]] (main) Waiting for database connection to become available at jdbc:mariadb://localhost:32791/mydb using query 'SELECT 1'
2021-01-18 17:06:24,613 INFO [ .3.6]] (main) Container is started (JDBC URL: jdbc:mariadb://localhost:32791/mydb)
2021-01-18 17:06:24,613 INFO [ .3.6]] (main) Container mariadb:10.3.6 started in PT8.651928S
The important part of these lines is that a MariaDB Docker container is started automatically. Then tests are executed using this instance as a database and finally, the instance is stopped.
Out-of-the-box Quarkus Test Resources
Some of the Quarkus extensions provide already implemented Quarkus Test Resources we can use in our tests. In the following table, the most important ones are shown:
Purpose |
Dependency |
Quarkus Test Resource |
Description |
SQL |
|
|
Starts a local H2 instance in server mode |
LDAP |
io.quarkus:quarkus-test-ldap |
LdapServerTestResource |
Starts a local InMemory LDAP. with |
Kubernetes |
|
|
Starts a local stub of Kubernetes API server and sets the proper environment variables needed by Kubernetes Client. You can record the expectations by injecting the following instance in your test:
|
Vault |
|
|
Starts a Hashicorp Vault instance |
JMS |
|
|
Starts a local Embedded Artemis instance |
SQL |
|
|
Starts a local Derby instance |
We’ve covered only persistence tests, but we can use Quarkus Test Resouces for any dependencies our tests might need like a Kafka cluster, a Mail server, or a service developed by another team.
At this point, we know how to write integration persistence tests using the same database server used on production, but we still have a typical use case in services architecture that hasn’t been covered yet in the article. How do we test the communication between services? Let’s see how to do it in the following section.
REST Client
Let’s add a new restriction to our registration service. Suppose there is a service that checks if a user name is banned from being used (offensive nicknames, invalid chars, …) and we need to call it before a new user is inserted into our system to avoid any violation of the rules. In the following figure, you can see what the system looks like:
Banned User Service has a simple endpoint that returns true if a username is invalid or false otherwise. The endpoint is a GET /api/<username>
.
Quarkus integrates with the MicroProfile Rest Client specification through the quarkus-rest-client
extension to provide a type-safe approach to invoke RESTful services over HTTP. The scope of this article isn’t to explain how to develop a Rest Client in Quarkus, hence only the important bits for testing purposes are shown. Let’s implement the logic:
The first thing to do is to create an interface mapping the REST interactions.
@Path("/api")
@RegisterRestClient
@ApplicationScoped
public interface BannedUserClient {
@GET @Path("/{username}")
@Produces(MediaType.TEXT_PLAIN)
String isBanned(@PathParam("username") String username);
}
Then a CDI bean is created to wrap the interaction with the external service, although in this case, it’s pretty straightforward, in other cases, it might need more complicated logic.
@ApplicationScoped
public class BannedUserService {
@RestClient
BannedUserClient bannedUserClient;
public boolean isBanned(String username) {
String banned = bannedUserClient.isBanned(username);
return Boolean.valueOf(banned);
}
}
Registration endpoint is updated with these changes:
@Path("/registration")
public class RegistrationResource {
@Inject
BannedUserService bannedUserService;
@POST
@Transactional
@Consumes(MediaType.APPLICATION_JSON)
public Response insertUser(User user) {
if (bannedUserService.isBanned(user.username)) {
return Response.status(Status.PRECONDITION_FAILED.getStatusCode())
.build();
} else {
…
}
}
Finally, the Banned User Service hostname is configured in the application.properties
file.
org.acme.BannedUserClient/mp-rest/url=http://banned-user-service
At this point, we need to update the RegistrationResourceTest
with the introduced changes. And we have 4 possible options:
- Change nothing and run the test against an already running instance of Banned User Service. That’s the easiest way, yet the one you can end up with flaky tests if the service has a downtime.
- Start a local instance of Banned User Service. That’s a fair strategy and might work in simple services, but if the service has dependencies on other services or databases, then it might be hard to run and maintain these tests.
- Mock the Rest Client. This is the most used strategy as it’s easy to use and has no external dependency.
- Using Service Virtualization tooling to not mock at the object level but at the network level.
The latter two options are the most used when testing services architecture, so let’s see how to implement them in Quarkus tests:
Mocks
We’ve seen mocks in part 1 of this Quarkus Testing series, but mocking the Rest client interface requires a small change.
Remember that to use Mockito we need to register io.quarkus:quarkus-junit5-mockito dependency into the build tool.
The biggest difference when mocking the Rest client interface in contrast to mocking a simple CDI bean is that org.eclipse.microprofile.rest.client.inject.RestClient
annotation is required together with io.quarkus.test.junit.mockito.InjectMock
.
Let’s write a test that verifies when a username is invalid, a 421 Precondition Failed
status code is returned.
@QuarkusTest
@TestHTTPEndpoint(RegistrationResource.class)
@QuarkusTestResource(MariaDBResource.class)
public class RegistrationResourceTest {
@InjectMock
@RestClient
BannedUserClient bannedUserClient;
@Test
public void shouldNotAddAUserIfBannedName() {
Mockito.when(bannedUserClient.isBanned("Alex")).thenReturn("true");
final User user = new User();
user.username = "Alex";
user.email = "asotobu@example.com";
given()
.body(user)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.when().post()
.then()
.statusCode(Status.PRECONDITION_FAILED.getStatusCode());
}
}
org.eclipse.microprofile.rest.client.inject.RestClient
andio.quarkus.test.junit.mockito.InjectMock
are used together to inject a mock of the Rest Client interface.org.mockito.Mockito
class is used as we normally do.
The main benefit of this approach is that it’s really easy to implement without any performance penalties. This works in most of our tests, but when we’re writing integration tests for the Rest client, we don’t want to mock the interface because what we really want is to test the full call stack, from code to network and back. Using the real service might not be an option for the reasons we saw before, so this is when service virtualization enters into the scene:
Service Virtualization
Service virtualization is a technique used to simulate the behavior of dependencies of a service. Although service virtualization is commonly associated with REST API-based services, the same concept can be applied to any other kind of dependencies like databases, ESBs, JMS, …
Service virtualization creates a server proxy where any request is intercepted and a canned response is provided to the caller. From the point of view of the service under test, the request is sent to a real server so all stack is tested.
Let’s see how we can use service virtualization with Quarkus.
Hoverfly
Hoverfly is an open-source, lightweight, service virtualization API simulation tool written in the Go programming language. It also offers language bindings that tightly integrate with Java (https://docs.hoverfly.io/projects/hoverfly-java/en/latest/).
To use the Hoverfly, we need to add the Hoverfly dependencies in our build tool script. For example in Maven you should add the following section in pom.xml
:
<dependency>
<groupId>io.specto</groupId>
<artifactId>hoverfly-java</artifactId>
<version>0.14.0</version>
<scope>test</scope>
</dependency>
Then let’s develop a Quarkus Test Resource that starts/stops the Hoverfly server proxy and records some canned answers.
import static io.specto.hoverfly.junit.core.HoverflyConfig.localConfigs;
import static io.specto.hoverfly.junit.core.SimulationSource.dsl;
import static io.specto.hoverfly.junit.dsl.HoverflyDsl.service;
import static io.specto.hoverfly.junit.dsl.ResponseCreators.success;
import static io.specto.hoverfly.junit.core.HoverflyMode.SIMULATE;
import io.specto.hoverfly.junit.core.Hoverfly;
public class HoverflyResource implements QuarkusTestResourceLifecycleManager {
private Hoverfly hoverfly;
@Override
public Map<String, String> start() {
hoverfly = new Hoverfly(localConfigs().destination("banned-user-service"), SIMULATE);
hoverfly.start();
hoverfly.simulate(
dsl(
service("banned-user-service")
.get("/api/Alex")
.willReturn(success("true", MediaType.TEXT_HTML))
.get("/api/Ada")
.willReturn(success("false", MediaType.TEXT_HTML))
)
);
return null;
}
@Override
public void stop() {
hoverfly.close();
}
}
Hoverfly
class is used to control the lifecycle of server proxy.Service
method sets the hostname under simulation. That’s the host configured in application.properties.- Two interactions are recorded, one returning the username is banned and the other not.
Now, we can remove the mocking reference of the RegistrationResourceTest
test and annotate it with HoverflyResource
.
@QuarkusTest
@TestHTTPEndpoint(RegistrationResource.class)
@QuarkusTestResource(MariaDBResource.class)
@QuarkusTestResource(HoverflyResource.class)
public class RegistrationResourceTest {
@InjectMock
PasswordGenerator passwordGenerator;
@Test
public void shouldNotAddAUserIfBannedName() {
final User user = new User();
user.username = "Alex";
user.email = "a@example.com";
given()
.body(user)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON)
.when().post()
.then()
.statusCode(Status.PRECONDITION_FAILED.getStatusCode());
}
}
There is no mocking code, the test just assumes a remote service is up and running. In fact, there is one—not the real Banned Users Service but a proxied one. Inspecting the console, you’ll see Hoverfly running:
2021-01-19 14:55:52,716 INFO [hoverfly] (Thread-43) Default proxy port has been overwritten port=55949
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) Default admin port has been overwritten port=55950
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) Using memory backend
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) Proxy prepared... Destination=. Mode=simulate ProxyPort=55949
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) current proxy configuration destination=. mode=simulate port=55949
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) serving proxy
2021-01-19 14:55:52,717 INFO [hoverfly] (Thread-43) Admin interface is starting... AdminPort=55950
2021-01-19 14:55:52,776 INFO [io.spe.hov.jun.cor.Hoverfly] (main) A local Hoverfly with version v1.3.1 is ready
2021-01-19 14:55:52,782 INFO [hoverfly] (Thread-43) Mode has been changed mode=simulate
Hoverfly is started and canned answers are recorded. Notice this test has two Quarkus Test Resources registered, one for MariaDB and another one for Hoverfly.
Conclusions
We’ve been digging into Quarkus testing, how to bypass security constraints for testing purposes, how to use Testcontainers for writing integration tests, and finally how to test when a service has a dependency on another service.
But there are still a few testing tips and tricks in Quarkus not yet covered. For example, how to test reactive/synchronous code when using Kafka or when periodic tasks are set. We will cover these topics in part 3 of this article.
About the Author
Alex Soto is a Director of Developer Experience at Red Hat. He is passionate about the Java world, software automation and he believes in the open-source software model. Alex is the co-author of Testing Java Microservices and Quarkus cookbook books and contributor to several open-source projects. A Java Champion since 2017, he is also an international speaker and teacher at Salle URL University. You can follow him on Twitter (@alexsotob) to stay tuned to what’s going on in Kubernetes and Java world.