§测试 Web 服务客户端
编写 Web 服务客户端需要大量的代码 - 准备请求、序列化和反序列化主体、设置正确的标头。由于很多代码都与字符串和弱类型映射一起使用,因此测试它非常重要。但是测试它也带来了一些挑战。一些常见的方法包括
§针对实际 Web 服务进行测试
当然,这会对客户端代码产生最高级别的信心,但是通常不切实际。如果它是第三方 Web 服务,可能存在速率限制,以防止您的测试运行(并且针对第三方服务运行自动化测试不被认为是良好的网络公民)。可能无法在该服务上设置或确保您的测试所需的必要数据的存在,并且您的测试可能会对该服务产生不良的副作用。
§针对 Web 服务的测试实例进行测试
这比前一个方法好一点,但是它仍然存在一些问题。许多第三方 Web 服务不提供测试实例。这也意味着您的测试依赖于测试实例正在运行,这意味着测试服务可能会导致您的构建失败。如果测试实例位于防火墙后面,它还会限制测试可以从哪里运行。
§模拟 HTTP 客户端
这种方法对测试代码的信心最低 - 通常这种测试仅限于测试代码是否按预期执行,这毫无价值。针对模拟 Web 服务客户端的测试表明代码运行并执行某些操作,但不能保证代码执行的任何操作是否实际上与正在进行的有效 HTTP 请求相关联。
§模拟 Web 服务
这种方法是在实际 Web 服务上进行测试和模拟 HTTP 客户端之间的一个很好的折衷方案。您的测试将表明它发出的所有请求都是有效的 HTTP 请求,主体序列化/反序列化工作正常,等等,但它们将完全自包含,不依赖于任何第三方服务。
Play 提供了一些用于在测试中模拟 Web 服务的辅助工具,使这种测试方法成为一个非常可行且有吸引力的选择。
§测试 GitHub 客户端
例如,假设您编写了一个 GitHub 客户端,并且想要对其进行测试。该客户端非常简单,它只允许您查找公共存储库的名称
import com.fasterxml.jackson.databind.JsonNode;
import java.util.*;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import javax.inject.Inject;
import play.libs.ws.WSClient;
class GitHubClient {
private WSClient ws;
@Inject
public GitHubClient(WSClient ws) {
this.ws = ws;
}
String baseUrl = "https://api.github.com";
public CompletionStage<List<String>> getRepositories() {
return ws.url(baseUrl + "/repositories")
.get()
.thenApply(
response ->
response.asJson().findValues("full_name").stream()
.map(JsonNode::asText)
.collect(Collectors.toList()));
}
}
请注意,它将 GitHub API 基本 URL 作为参数 - 我们将在测试中覆盖它,以便我们可以将其指向我们的模拟服务器。
为了测试这一点,我们需要一个嵌入式 Play 服务器来实现此端点。我们可以通过 创建嵌入式服务器 和 路由 DSL 来实现。
Server server =
Server.forRouter(
(components) ->
RoutingDsl.fromComponents(components)
.GET("/repositories")
.routingTo(
request -> {
ArrayNode repos = Json.newArray();
ObjectNode repo = Json.newObject();
repo.put("full_name", "octocat/Hello-World");
repos.add(repo);
return ok(repos);
})
.build());
我们的服务器现在正在一个随机端口上运行,我们可以通过 httpPort
方法访问它。我们可以使用它构建要传递给 GitHubClient
的基本 URL,但是 Play 具有更简单的机制。 WSTestClient
类提供了一个 newClient
方法,该方法接受一个端口号。当使用客户端向相对 URL(例如 /repositories
)发出请求时,此客户端会将该请求发送到传递的端口上的 localhost。这意味着我们可以在 GitHubClient
上将基本 URL 设置为 ""
。这也意味着如果客户端返回带有指向其他资源的 URL 链接的资源,然后客户端使用这些链接发出进一步的请求,我们可以确保这些链接是相对 URL 并按原样使用它们。
因此,我们现在可以在 @Before
注释的方法中创建服务器、WS 客户端和 GitHubClient
,并在 @After
注释的方法中关闭它们,然后我们可以在测试中测试客户端
import static org.hamcrest.core.IsCollectionContaining.*;
import static org.junit.Assert.*;
import static play.mvc.Results.*;
import com.fasterxml.jackson.databind.node.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import play.libs.Json;
import play.libs.ws.*;
import play.routing.RoutingDsl;
import play.server.Server;
public class GitHubClientTest {
private GitHubClient client;
private WSClient ws;
private Server server;
@Before
public void setup() {
server =
Server.forRouter(
(components) ->
RoutingDsl.fromComponents(components)
.GET("/repositories")
.routingTo(
request -> {
ArrayNode repos = Json.newArray();
ObjectNode repo = Json.newObject();
repo.put("full_name", "octocat/Hello-World");
repos.add(repo);
return ok(repos);
})
.build());
ws = play.test.WSTestClient.newClient(server.httpPort());
client = new GitHubClient(ws);
client.baseUrl = "";
}
@After
public void tearDown() throws IOException {
try {
ws.close();
} finally {
server.stop();
}
}
@Test
public void repositories() throws Exception {
List<String> repos = client.getRepositories().toCompletableFuture().get(10, TimeUnit.SECONDS);
assertThat(repos, hasItem("octocat/Hello-World"));
}
}
§返回文件
在前面的示例中,我们手动构建了模拟服务的 json。通常,最好捕获您正在测试的服务的实际响应,并返回它。为了帮助您实现这一点,Play 提供了一个 sendResource
方法,该方法允许您轻松地从类路径上的文件创建结果。
在对实际的 GitHub API 发出请求后,创建一个文件将其存储在测试资源目录中。测试资源目录是 test/resources
(如果您使用的是 Play 目录布局)或 src/test/resources
(如果您使用的是标准 sbt 目录布局)。在本例中,我们将将其命名为 github/repositories.json
,它将包含以下内容
[
{
"id": 1296269,
"owner": {
"login": "octocat",
"id": 1,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"description": "This your first repo!",
"private": false,
"fork": false,
"url": "https://api.github.com/repos/octocat/Hello-World",
"html_url": "https://github.com/octocat/Hello-World"
}
]
您可以根据自己的测试需求对其进行修改,例如,如果您的 GitHub 客户端使用上述响应中的 URL 向其他端点发出请求,您可能需要从这些 URL 中删除 https://api.github.com
前缀,以便它们也成为相对 URL,并由测试客户端自动路由到 localhost 的正确端口。
现在,修改路由器以提供此资源
Server server =
Server.forRouter(
(components) ->
RoutingDsl.fromComponents(components)
.GET("/repositories")
.routingTo(request -> ok().sendResource("github/repositories.json"))
.build());
请注意,由于文件名扩展名为 .json
,Play 将自动设置 application/json
的内容类型。
下一步:日志记录
发现此文档中的错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?请访问 我们的社区论坛,与社区展开对话。