1、回顾JVM模式下基于HTTP的测试
在 pom.xml 文件中,有2个测试依赖项:
<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测试是必需的,因为它提供了@QuarkusTest控制测试框架的注释。 rest-assured不是必需的,但它是测试HTTP端点的便捷方法,还提供了集成功能,该功能会自动设置正确的URL,因此无需进行配置。
因为使用的是JUnit 5,所以 必须设置Surefire Maven插件的版本,因为默认版本不支持Junit 5:
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
设置java.util.logging.manager系统属性,以确保测试将使用正确的logmanager,并maven.home确保${maven.home}/conf/settings.xml应用了from的自定义配置(如果有)。
该项目还应该包含一个简单的测试:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello"));
}
@Test
public void testGreetingEndpoint() {
String uuid = UUID.randomUUID().toString();
given()
.pathParam("name", uuid)
.when().get("/hello/greeting/{name}")
.then()
.statusCode(200)
.body(is("hello " + uuid));
}
}
该测试使用HTTP直接测试我们的REST端点。运行测试时,将在运行测试之前启动应用程序。
1.1、控制测试端口
尽管 Quarkus 默认会监听 8080 端口,但运行测试时默认为 8081 。 这使您可以在运行应用程序时运行测试。
可以通过在您的quarkus.http.test-port服务器中配置HTTP和quarkus.http.test-ssl-portHTTPS来配置测试使用的端口application.properties:
quarkus.http.test-port=8083
quarkus.http.test-ssl-port=8446
Quarkus还提供了 RestAssured 集成,会在运行测试之前配置 RestAssured 使用的默认端口,因此不需要其他配置。
1.2、控制HTTP超时
在测试中使用“ REST安全”时,连接和响应超时设置为30秒。可以使用quarkus.http.test-timeout属性覆盖此设置:
quarkus.http.test-timeout=10s
1.3、注入URI
也有可能直接将URL注入测试中,从而可以轻松使用其他客户端。这是通过@TestHTTPResource注释完成的。
写一个简单的测试,展示一下如何加载一些静态资源。首先在中创建一个简单的HTML文件 src/main/resources/META-INF/resources/index.html:
<html>
<head>
<title>Testing Guide</title>
</head>
<body>
Information about testing
</body>
</html>
创建一个简单的测试,以确保正确提供该测试:
package org.acme.getting.started.testing;
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("index.html")
URL url;//这个注解允许您直接注入Quarkus实例的URL,注解的值将是URL的路径部分
@Test
public void testIndexHtml() throws Exception {
try (InputStream in = url.openStream()) {
String contents = readStream(in);
Assertions.assertTrue(contents.contains("<title>Testing Guide</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);
}
}
现在@TestHTTPResource,您可以注入URI,URL和StringURL的表示。
2、测试特定的端点
RESTassured和@TestHTTPResource都允许您指定要测试的端点类,而不是硬编码路径。这目前支持JAX-RS端点、servlet和反应式路由。这使得更容易准确地看到给定测试正在测试的端点。
出于这些示例的目的,假设有一个如下所示的端点:
@Path("/hello")
public class GreetingResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return "hello";
}
}
当前,不支持@ApplicationPath()用于设置JAX-RS上下文路径的注释。quarkus.resteasy.path如果要自定义上下文路径,请改用 config值。
2.1、TestHTTPResource
可以使用io.quarkus.test.common.http.TestHTTPEndpoint注解指定端点路径,然后将从提供的端点中提取路径。如果您还为TestHTTPResource端点指定一个值,它将被附加到端点路径的末尾。
package org.acme.getting.started.testing;
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.TestHTTPEndpoint;
import io.quarkus.test.common.http.TestHTTPResource;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class StaticContentTest {
@TestHTTPEndpoint(GreetingResource.class)
@TestHTTPResource
URL url;
@Test
public void testIndexHtml() throws Exception {
try (InputStream in = url.openStream()) {
String contents = readStream(in);
Assertions.assertTrue(contents.equals("hello"));
}
}
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);
}
}
因为GreetingResource使用注释,@Path("/hello")所以注入的URL将以结尾/hello。
2.2、RESTassured
要控制RESTassured基本路径(即,作为每个请求的根的默认路径),可以使用io.quarkus.test.common.http.TestHTTPEndpoint注释。这可以在类或方法级别上应用。要测试问候资源,我们将执行以下操作:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.common.http.TestHTTPEndpoint;
import org.junit.jupiter.api.Test;
import java.util.UUID;
import static io.restassured.RestAssured.when;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
@TestHTTPEndpoint(GreetingResource.class) //这告诉RESTAssured给所有请求加上前缀/hello。
public class GreetingResourceTest {
@Test
public void testHelloEndpoint() {
when().get() //请注意,无需在此处指定路径,/hello是此测试的默认路径
.then()
.statusCode(200)
.body(is("hello"));
}
}
3、注入测试
Quarkus 可以通过 @Inject 注解将 CDI bean 注入到测试中 (事实上,Quarkus 中的测试是完整的 CDI bean,因此您可以使用所有 CDI 功能)。创建一个简单的测试,不使用 HTTP 直接测试接口:
package org.acme.getting.started.testing;
import javax.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class GreetingServiceTest {
@Inject //该GreetingServicebean将被注入到测试
GreetingService service;
@Test
public void testGreetingService() {
Assertions.assertEquals("hello Quarkus", service.greeting("Quarkus"));
}
}
4、将拦截器应用于测试
如上所述,Quarkus 测试实际上是完整的 CDI bean, 因此可以像平常一样应用 CDI 拦截器。 比如,如果希望测试方法在事务的上下文中运行,则可以简单地在方法加上注解 @Transactional ,事务拦截器将对其进行处理。
除此之外,还可以创建自己的测试原型 (stereotypes)。 例如,可以创建 @TransactionalQuarkusTest 如下:
@QuarkusTest
@Stereotype
@Transactional
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TransactionalQuarkusTest {
}
如果随后将此注释应用于测试类,则其作用就像我们同时应用了@QuarkusTest和 @Transactional注释一样,例如:
@TransactionalQuarkusTest
public class TestStereotypeTestCase {
@Inject
UserTransaction userTransaction;
@Test
public void testUserTransaction() throws Exception {
Assertions.assertEquals(Status.STATUS_ACTIVE, userTransaction.getStatus());
}
}
5、测试和事务
可以在测试上使用标准的Quarkus@Transactional注释,但这意味着测试对数据库所做的更改将是持久的。如果希望在测试结束时回滚所做的任何更改,可以使用io.quarkus.test.TestTransaction 注释。这将在事务中运行测试方法,但在测试方法完成后将其回滚以还原任何数据库更改。
6、丰富测试回调
作为拦截器的替代或补充,可以通过实现以下回调接口来丰富所有@QuarkusTest类:
- io.quarkus.test.junit.callback.QuarkusTestBeforeClassCallback
- io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallback
- io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback
- io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback
io.quarkus.test.junit.callback.QuarkusTestBeforeAllCallback为了支持io.quarkus.test.junit.callback.QuarkusTestAfterConstructCallbackQuarkus而弃用了该版本,并将在Quarkus的将来版本中将其删除
此类回调实现必须注册为所定义的“服务提供者” java.util.ServiceLoader
例如以下示例回调:
package org.acme.getting.started.testing;
import io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback;
import io.quarkus.test.junit.callback.QuarkusTestMethodContext;
public class MyQuarkusTestBeforeEachCallback implements QuarkusTestBeforeEachCallback {
@Override
public void beforeEach(QuarkusTestMethodContext context) {
System.out.println("Executing " + context.getTestMethod());
}
}
必须通过src/main/resources/META-INF/services/io.quarkus.test.junit.callback.QuarkusTestBeforeEachCallback以下方式进行注册:
org.acme.getting.started.testing.MyQuarkusTestBeforeEachCallback
- 可以从测试类或方法中读取注释,以控制回调应执行的操作。
- 尽管可以使用像这样的JUnit Jupiter回调接口BeforeEachCallback,但由于Quarkus必须在JUnit不知道的自定义类加载器中运行测试,因此您可能会遇到类加载问题
7、测试不同Profiles
到目前为止,在所有示例中,对于所有测试,仅一次启动Quarkus。在运行第一个测试之前,Quarkus将启动,然后所有测试都将运行,然后Quarkus将在最后关闭。这提供了非常快速的测试体验,但是由于无法测试不同的配置而受到了一定的限制。
为了解决这个问题,Quarkus支持测试配置文件的想法。如果测试的配置文件与之前运行的测试文件不同,则在运行测试之前,将关闭Quarkus并使用新的配置文件启动。这显然要慢一些,因为它增加了测试时间的关闭/启动周期,但具有很大的灵活性。
- 为了减少Quarkus需要重启的次数,建议您将所有需要特定配置文件的测试放入它们自己的包中,然后按字母顺序运行测试。
7.1、 编辑一个Profile
要实现测试配置文件,需要实现io.quarkus.test.junit.QuarkusTestProfile:
package org.acme.getting.started.testing;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import io.quarkus.test.junit.QuarkusTestProfile;
import io.quarkus.test.junit.QuarkusTestProfile.TestResourceEntry;
public class MockGreetingProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
//这种方法使可以覆盖配置属性。在这里,正在更改JAX-RS根路径。
return Collections.singletonMap("quarkus.resteasy.path","/api");
}
@Override
public Set<Class<?>> getEnabledAlternatives() {
//这种方法能够启用CDI @Alternativebean。这样可以轻松模拟出某些bean功能。
return Collections.singleton(MockGreetingService.class);
}
@Override
//这可用于更改配置文件。由于此默认设置test不执行任何操作,因此为了完整性起见将其包括在内。
public String getConfigProfile() {
return "test";
}
@Override
public List<TestResourceEntry> testResources() {
//此方法使我们可以应用仅适用于此配置文件的其他 QuarkusTestResourceLifecycleManager类。如果不重写此方法,
//则仅使用QuarkusTestResourceLifecycleManager通过@QuarkusTestResource类注释启用的类将用于使用此配置文件的测试
//(与完全不使用配置文件的测试的行为相同)。
return Collections.singletonList(new TestResourceEntry(CustomWireMockServerManager.class));
}
}
现在,已经定义了配置文件,需要将其包含在测试类中。使用@TestProfile(MockGreetingProfile.class)。
所有的测试配置文件配置都存储在一个类中,这使很容易分辨以前的测试是否以相同的配置运行。
7.2、运行特定的测试
Quarkus提供了将测试执行限制为带有特定@TestProfile注释的测试的功能 。通过利用与系统属性结合的tags方法来QuarkusTestProfile工作quarkus.test.profile.tags。
本质上,任何QuarkusTestProfile具有至少一个与值匹配的匹配标签的变量都quarkus.test.profile.tags将被认为是活动的,并且所有带有@TestProfile活动配置文件注释的测试都将运行,而其余测试将被跳过。在下面的示例中最好地显示了这一点。
首先,定义一些这样的QuarkusTestProfile实现:
public class Profiles {
public static class NoTags implements QuarkusTestProfile {
}
public static class SingleTag implements QuarkusTestProfile {
@Override
public Set<String> tags() {
return Collections.singleton("test1");
}
}
public static class MultipleTags implements QuarkusTestProfile {
@Override
public Set<String> tags() {
return new HashSet<>(Arrays.asList("test1", "test2"));
}
}
}
现在,假设我们有以下测试:
@QuarkusTest
public class NoQuarkusProfileTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.NoTags.class)
public class NoTagsTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.SingleTag.class)
public class SingleTagTest {
@Test
public void test() {
// test something
}
}
@QuarkusTest
@TestProfile(Profiles.MultipleTags.class)
public class MultipleTagsTest {
@Test
public void test() {
// test something
}
}
考虑以下情形:
- quarkus.test.profile.tags 未设置:将执行所有测试。
- quarkus.test.profile.tags=foo:在这种情况下,将不会执行任何测试,因为在QuarkusTestProfile实现上定义的所有标签均不匹配的值quarkus.test.profile.tags。请注意,NoQuarkusProfileTest由于未使用注释,因此也不执行@TestProfile。
- quarkus.test.profile.tags=test1:在这种情况下SingleTagTest,MultipleTagsTest将运行,因为它们各自QuarkusTestProfile实现上的标记与的值匹配quarkus.test.profile.tags。
- quarkus.test.profile.tags=test1,test3:这种情况导致执行与前一种情况相同的测试。
- quarkus.test.profile.tags=test2,test3:在这种情况下,只有MultipleTagsTest将运行,因为MultipleTagsTest是唯一的QuarkusTestProfile实现,其tags方法的价值相匹配quarkus.test.profile.tags。
8、模拟(Mock)支持
Quarkus支持使用两种不同的方法来使用模拟对象。可以使用CDI替代品为所有测试类模拟出一个bean,也可以QuarkusMock基于每个测试来模拟出一个bean 。
8.1、CDI@Alternative机制。
要使用它,只需在src/test/java目录中用类重写要模拟的bean,并在bean上添加@Alternative和@Priority(1)注释。或者,一个方便的io.quarkus.test.Mock 可以使用原型注释。这个内置的原型声明@Alternative、@Priority(1)和@Dependent。例如,有以下服务:
@ApplicationScoped
public class ExternalService {
public String service() {
return "external";
}
}
可以在下面的类中模拟它src/test/java:
@Mock
@ApplicationScoped //覆盖在@Dependent构造型上声明的范围@Mock。
public class MockExternalService extends ExternalService {
@Override
public String service() {
return "mock";
}
}
重要的是,替代项应出现在src/test/java目录中而不是中src/main/java,因为否则替代项将一直有效,而不仅是在测试时。
请注意,目前这种方法不适用于本机图像测试,因为这将需要将测试替代方法烘焙到本机图像中。
8.2、使用QuarkusMock模拟
io.quarkus.test.junit.QuarkusMock类可用于暂时模拟出任何正常范围的Bean。如果在方法中使用此方法,则@BeforeAll该模拟将对当前类的所有测试生效,而如果在测试方法中使用此方法,则该模拟将仅在当前测试期间生效。
此方法可用于任何正常范围的CDI bean(例如@ApplicationScoped,@RequestScoped等等,基本上除了@Singleton和之外的每个范围@Dependent)。
用法示例如下所示:
@QuarkusTest
public class MockTestCase {
@Inject
MockableBean1 mockableBean1;
@Inject
MockableBean2 mockableBean2;
@BeforeAll
public static void setup() {
MockableBean1 mock = Mockito.mock(MockableBean1.class);
Mockito.when(mock.greet("Stuart")).thenReturn("A mock for Stuart");
QuarkusMock.installMockForType(mock, MockableBean1.class);
//由于此处无法使用注入的实例installMockForType,因此该模拟方法可用于两种测试方法
}
@Test
public void testBeforeAll() {
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Hello Stuart", mockableBean2.greet("Stuart"));
}
@Test
public void testPerTestMock() {
//installMockForInstance用来替换注入的bean,这在测试方法期间有效。
QuarkusMock.installMockForInstance(new BonjourGreeter(), mockableBean2);
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Bonjour Stuart", mockableBean2.greet("Stuart"));
}
@ApplicationScoped
public static class MockableBean1 {
public String greet(String name) {
return "Hello " + name;
}
}
@ApplicationScoped
public static class MockableBean2 {
public String greet(String name) {
return "Hello " + name;
}
}
public static class BonjourGreeter extends MockableBean2 {
@Override
public String greet(String name) {
return "Bonjour " + name;
}
}
}
请注意,它不依赖Mockito,可以使用任何喜欢的模拟库,甚至可以手动覆盖对象以提供所需的行为。
8.2.1、进一步简化@InjectMock
QuarkusMockQuarkus在提供的功能的基础上,还允许用户毫不费力地利用Mockito来模拟Mockito支持的bean QuarkusMock。可通过依赖项中可用的@io.quarkus.test.junit.mockito.InjectMock注释来使用此功能quarkus-junit5-mockito。
使用@InjectMock,上一个示例可以编写如下:
@QuarkusTest
public class MockTestCase {
@InjectMock
MockableBean1 mockableBean1;
@InjectMock
MockableBean2 mockableBean2;
@BeforeEach
public void setup() {
Mockito.when(mockableBean1.greet("Stuart")).thenReturn("A mock for Stuart");
}
@Test
public void firstTest() {
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals(null, mockableBean2.greet("Stuart"));
}
@Test
public void secondTest() {
Mockito.when(mockableBean2.greet("Stuart")).thenReturn("Bonjour Stuart");
Assertions.assertEquals("A mock for Stuart", mockableBean1.greet("Stuart"));
Assertions.assertEquals("Bonjour Stuart", mockableBean2.greet("Stuart"));
}
@ApplicationScoped
public static class MockableBean1 {
public String greet(String name) {
return "Hello " + name;
}
}
@ApplicationScoped
public static class MockableBean2 {
public String greet(String name) {
return "Hello " + name;
}
}
}
- @InjectMock导致模拟存在和是在测试类的试验方法可用的(其它测试类不影响此)
- 在mockableBean1此为该类的每种测试方法配置
- 由于mockableBean2尚未配置模拟,它将返回默认的Mockito响应。
- 在此测试中,mockableBean2已配置,因此它将返回已配置的响应。
尽管上面的测试很好地展示了的功能@InjectMock,但它并不是真实测试的良好表示。在真实的测试中,很可能会配置一个模拟,但是然后测试一个使用模拟的bean的bean。这是一个例子:
@QuarkusTest
public class MockGreetingServiceTest {
@InjectMock
GreetingService greetingService;
@Test
public void testGreeting() {
when(greetingService.greet()).thenReturn("hi");
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hi"));
//由于将其配置greetingService为GreetingResource使用GreetingServicebean的模拟,
//因此得到了模拟响应,而不是常规GreetingServicebean的响应。
}
@Path("greeting")
public static class GreetingResource {
final GreetingService greetingService;
public GreetingResource(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GET
@Produces("text/plain")
public String greet() {
return greetingService.greet();
}
}
@ApplicationScoped
public static class GreetingService {
public String greet(){
return "hello";
}
}
}
8.2.2、在@InjectSpy中使用Spies而不是mock
在InjectMockQuarkus提供的功能的基础上,Quarkus还允许用户毫不费力地利用Mockito来监视由Mockito支持的bean QuarkusMock。可通过依赖项中可用的@io.quarkus.test.junit.mockito.InjectSpy注释来使用此功能quarkus-junit5-mockito。
有时,在测试时,只需要验证是否采用了某个逻辑路径,或者只需要在执行Spied克隆上的其余方法的同时对单个方法的响应进行存根即可。请参阅Mockito文档以获取有关Spy部分模拟的更多详细信息。在这两种情况下,最好都使用间谍对象。使用@InjectSpy,上一个示例可以编写如下:
@QuarkusTest
public class SpyGreetingServiceTest {
@InjectSpy
GreetingService greetingService;
@Test
public void testDefaultGreeting() {
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hello"));
Mockito.verify(greetingService, Mockito.times(1)).greet();
//只是要确保GreetingService此测试调用了对我们的greet方法,而不是覆盖该值。
}
@Test
public void testOverrideGreeting() {
when(greetingService.greet()).thenReturn("hi");
//告诉间谍返回“ hi”而不是“ hello”。当GreetingResource请求来自GreetingService的问候时,
//得到的是模拟响应,而不是常规GreetingServiceBean的响应。
given()
.when().get("/greeting")
.then()
.statusCode(200)
.body(is("hi")); //正在验证是否从间谍获得了模拟的响应。
}
@Path("greeting")
public static class GreetingResource {
final GreetingService greetingService;
public GreetingResource(GreetingService greetingService) {
this.greetingService = greetingService;
}
@GET
@Produces("text/plain")
public String greet() {
return greetingService.greet();
}
}
@ApplicationScoped
public static class GreetingService {
public String greet(){
return "hello";
}
}
}
8.2.3、使用@InjectMock与@RestClient
这些@RegisterRestClient寄存器在运行时注册rest-client的实现,并且由于Bean需要是常规作用域,因此必须使用注释接口@ApplicationScoped。
@Path("/")
@ApplicationScoped
@RegisterRestClient
public interface GreetingService {
@GET
@Path("/hello")
@Produces(MediaType.TEXT_PLAIN)
String hello();
}
对于测试类,这是一个示例:
@QuarkusTest
public class GreetingResourceTest {
@InjectMock
@RestClient //指示此注入点旨在使用的实例RestClient。
GreetingService greetingService;
@Test
public void testHelloEndpoint() {
Mockito.when(greetingService.hello()).thenReturn("hello from mockito");
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("hello from mockito"));
}
}
9、在Quarkus应用程序启动之前启动服务
一个非常普遍的需求是在Quarkus应用程序开始进行测试之前,启动Quarkus应用程序所依赖的某些服务。为了满足这一需求,Quarkus提供了@io.quarkus.test.common.QuarkusTestResource和io.quarkus.test.common.QuarkusTestResourceLifecycleManager。
通过简单地用注释测试套件中的任何测试@QuarkusTestResource,Quarkus将在运行QuarkusTestResourceLifecycleManager任何测试之前运行相应的测试。测试套件还可以自由使用多个@QuarkusTestResource批注,在这种情况下,所有相应的QuarkusTestResourceLifecycleManager对象都将在测试之前运行。当使用多个测试资源时,它们可以同时启动。为此,您需要设置@QuarkusTestResource(parallel = true)。
测试资源是全局的,即使它们是在测试类或自定义配置文件上定义的也是如此,这意味着即使我们确实删除了重复项,所有测试都将激活它们。如果只想在单个测试类或测试配置文件上启用测试资源,则可以使用@QuarkusTestResource(restrictToAnnotatedClass = true)。
Quarkus提供了一些现成的实现QuarkusTestResourceLifecycleManager(请参阅io.quarkus.test.h2.H2DatabaseTestResource哪个启动了H2数据库,或者io.quarkus.test.kubernetes.client.KubernetesMockServerTestResource哪个启动了模拟的Kubernetes API服务器),但是创建自定义实现来满足特定的应用程序需求是很常见的。常见情况包括使用Testcontainers启动docker容器(可在此处找到示例),或使用Wiremock启动模拟HTTP服务器(可在此处找到示例)。
9.1、基于注释的测试资源
可以编写使用注释启用和配置的测试资源。可以通过@QuarkusTestResource 在注释上放置来启用该注释,该注释将用于启用和配置测试资源。
例如,这定义了@WithKubernetesTestServer注释,您可以在测试中使用该注释来激活KubernetesServerTestResource,但仅用于带注释的测试类。您也可以将它们放在QuarkusTestProfile测试配置文件中。
@QuarkusTestResource(KubernetesServerTestResource.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WithKubernetesTestServer {
/**
* Start it with HTTPS
*/
boolean https() default false;
/**
* Start it in CRUD mode
*/
boolean crud() default true;
/**
* Port to use, defaults to any available port
*/
int port() default 0;
}
本KubernetesServerTestResource类必须实现 QuarkusTestResourceConfigurableLifecycleManager以使用先前的注解配置界面:
public class KubernetesServerTestResource
implements QuarkusTestResourceConfigurableLifecycleManager<WithKubernetesTestServer> {
private boolean https = false;
private boolean crud = true;
private int port = 0;
@Override
public void init(WithKubernetesTestServer annotation) {
this.https = annotation.https();
this.crud = annotation.crud();
this.port = annotation.port();
}
// ...
}
10、挂起检测
@QuarkusTest支持挂起检测以帮助诊断任何意外的挂起。如果在指定时间内未取得任何进展(即未调用JUnit回调),则Quarkus将向控制台打印堆栈跟踪以帮助诊断挂起。此超时的默认值为10分钟。
将不会采取进一步的措施,并且测试将继续正常进行(通常直到CI超时为止),但是打印出的堆栈跟踪信息应有助于诊断构建失败的原因。您可以使用quarkus.test.hang-detection-timeout系统属性来控制此超时 (您也可以在application.properties中进行设置,但是直到Quarkus启动后才会读取该超时,因此Quarkus启动的超时将默认为10分钟)。
11、本机可执行测试
也可以使用来测试本机可执行文件@NativeImageTest。它支持本指南中提到的所有功能,除了注入测试外(本机可执行文件在单独的非JVM进程中运行,这实际上是不可能的)。
12、使用@QuarkusIntegrationTest
@QuarkusIntegrationTest应该用于启动和测试Quarkus构建产生的工件,并支持测试jar(任何类型),本机映像或容器映像。简而言之,这意味着如果Quarkus构建(mvn package或gradle build)的结果是一个jar,该jar将作为启动,java -jar …并对其进行测试。相反,如果构建了本机映像,则将在启动应用程序时./application …再次针对正在运行的应用程序运行测试。最后,如果在构建过程中创建了容器映像(通过使用包括quarkus-container-image-jib或quarkus-container-image-docker扩展名并quarkus.container-image.build=true配置了 属性),则将创建并运行容器(这需要存在docker可执行文件)。
与@NativeImageTest一样,这是一个黑盒测试,它支持相同的设置功能并具有相同的限制。