4
Testing Quarkus Apps
In this step we’ll show you how to effectively write functional and unit tests for your Quarkus Apps.
You should already have a completed test written for you, including the correct pom.xml
setup where you should see 2 test dependencies:
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
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.
The basic test is in the GreetingResourceTest
(in the src/test/java/org/acme/people
directory). For example:
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
}
When creating new tests, always double-check to be sure you are in the |
Add a new test
Add a new test in the existing GreetingResourceTest.java
class under the // add more tests
marker that tests the /hello/greeting
endpoint by copying this code below the existing test:
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(startsWith("hello " + uuid));
}
Test the app
Let’s test our app. Click on the Run Tests command:
The tests will run, and eventually complete. Did you get any errors? You should have! You probably got:
[ERROR] Failures:
[ERROR] GreetingResourceTest.testHelloEndpoint:20 1 expectation failed.
Response body doesn't match expectation.
Expected: is "hello"
Actual: hola
This is because you changed the greeting in an earlier step. In GreetingResource
, change hola
back to hello
(in the GreetingResource
class) and re-run the test and confirm it passes with BUILD SUCCESS
using the same command:
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 (which you just did - your app is still running from the previous exercises).
You can configure the port used by tests by configuring quarkus.http.test-port
in your application.properties
. Open that file (it’s in src/main/resources
) and add a new line at the end:
quarkus.http.test-port=8083
You may see yellow lines underneath the This is due to not yet installing any database extensions. That’s part of the Advanced labs which you may or may not be doing today, so you can ignore them for now! |
Now re-run the tests (click on Run Tests again) and look for
INFO [io.quarkus] (main) Quarkus x.xx.x.Final started in 1.131s. Listening on: http://0.0.0.0:8083
Notice the port 8083
.
You will also get a pop-up letting you know CodeReady detected the app opened a new port on |
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.
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/
. Right-click on this directory and select New → File. Name the file test.html
in the dialog box:
Add this code to the file:
<!DOCTYPE html>
<html>
<head>
<title>Testing with Quarkus</title>
</head>
<body>
<p>... it's fun and entertaining!</p>
</body>
</html>
Our test will verify that the <title>
tags contain the right content.
Next, create a new test under src/test/java
in the org.acme.people
package called StaticContentTest.java
. Replace this code to the file:
package org.acme.people;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class StaticContentTest {
@TestHTTPResource("test.html")
URL url;
@Test
public void testIndexHtml() throws Exception {
try (InputStream in = url.openStream()) {
String contents = readStream(in);
Assertions.assertTrue(contents.contains("<title>Testing with Quarkus</title>"));
}
}
private static String readStream(InputStream in) throws IOException {
byte[] data = new byte[1024];
int r;
ByteArrayOutputStream out = new ByteArrayOutputStream();
while ((r = in.read(data)) > 0) {
out.write(data, 0, r);
}
return new String(out.toByteArray(), StandardCharsets.UTF_8);
}
}
The @TestHTTPResource 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. |
Re-run the tests (with Run Tests) to ensure they’re still passing.
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.
Create a new test class file in src/test
in the org.acme.people
package called GreetingServiceTest.java
. Use the following code for the file’s contents (note we’ve included the proper imports for you):
package org.acme.people;
import javax.inject.Inject;
import org.acme.people.service.GreetingService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class GreetingServiceTest {
private static final Logger LOGGER = LoggerFactory.getLogger("GreetingServiceTest");
@Inject
GreetingService service;
@Test
public void testGreetingService() {
Assertions.assertTrue(service.greeting("Quarkus").startsWith("hello Quarkus"));
}
}
Here we are injecting our GreetingService and calling it, just as our RESTful resource endpoint does in the production code. |
Run the tests again (with Run Tests) to verify the new test passes.
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 In addition to this you can also create your own test stereotypes. Stereotypes can be particularly useful in large applications where you have a number of beans that perform similar functions, as it allows you to do something akin to multiple inheritance (multiple annotations) without having to repeat yourself over and over. For example we could create a
If we then apply this annotation to a test class it will act as if we had applied both the
|
Mock support
Quarkus supports the use of mock objects using the CDI @Alternative
mechanism. To use this simply override the bean you wish to mock with a class in the src/test/java
directory, and put the @Alternative
and @Priority(1)
annotations on the bean. Alternatively, a convenient io.quarkus.test.Mock
stereotype annotation could be used. This built-in stereotype declares @Alternative
, @Priority(1)
and @Dependent
.
Let’s mock our existing GreetingService
. Although our existing service is pretty simple, in the real world the service might have too many dependencies on external systems to be feasible to call directly.
Create a new class file in src/test/java
in the org.acme.people
package called MockGreetingService.java
with the following code:
package org.acme.people;
import javax.enterprise.context.ApplicationScoped;
import org.acme.people.service.GreetingService;
import io.quarkus.test.Mock;
@Mock
@ApplicationScoped
public class MockGreetingService extends GreetingService {
@Override
public String greeting(String name) {
return "hello " + name + " <<<<<<<<<< from mock greeting >>>>>>>>>>";
}
}
Now modify our existing GreetingServiceTest
class to add a log statement showing the value retrieved during the test. Modify the testGreetingService
method to look like:
@Test
public void testGreetingService() {
LOGGER.info("greeting: " + service.greeting("Quarkus"));
Assertions.assertTrue(service.greeting("Quarkus").startsWith("hello Quarkus"));
}
Basically we’ve added a new LOGGER.info
line.
Now run the tests again (with Run Tests) and watch the output closely - you will see:
INFO [GreetingServiceTest] (main) greeting: hello Quarkus <<<<<<<<<< from mock greeting >>>>>>>>>>
This confirms that our MockGreetingService
is being used instead of the original GreetingService
.
Congratulations!
In this section we covered basic testing of Quarkus Apps using the @QuarkusTest
and supporting annotations. This is an important part of any software engineering project and with Quarkus, testing has never been easier. For more information on testing with Quarkus, be sure to review the Quarkus Testing Guide.
In the next section we’ll talk about how to effectively debug Quarkus applications. On with the show!
No comments:
Post a Comment