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一样,这是一个黑盒测试,它支持相同的设置功能并具有相同的限制。
