一个基于 SwiftNIO 的 Web 服务器,旨在用作 UI 自动化测试中的 Mock 服务器。
在针对真实服务器运行 UI 测试时,可能会出现一些问题:网络可能不稳定,远程服务器上的内容可能会更改,某些测试场景可能需要在某些外部系统中执行操作,并且通常进行网络调用会减慢所有测试的速度。
SwiftNIOMock 旨在通过提供一个在 localhost
上运行的 Mock Web 服务器实现来解决这些问题,应用程序在 UI 测试下运行时应访问该服务器而不是真实的服务器。与其他解决方案(例如使用 URLProtocol
注册自定义协议)不同,使用 Mock 服务器只需要将应用程序切换到 localhost
,并为您提供更大的灵活性和控制权,因为服务器由测试控制,并且您可以从测试场景中更改其状态。
SwiftNIOMock 支持三种常见场景
使用 redirect
中间件将请求重定向到真实服务。 这可用于日志记录目的,这可以使调试更容易,因为所有网络日志都将在测试运行程序进程的控制台日志中。
使用 router
中间件 Mock 端点。 当您需要更好地控制 Mock 服务器的状态时,这非常有用,并且允许您完全 Mock 掉应用程序发出的所有网络请求(尽管它可以与 redirect
中间件一起使用)。 当测试场景需要在某些外部系统中执行操作时,通常需要这样做。 当您完全控制服务器的状态时,您可以轻松伪造此类操作。
使用 redirect
中间件记录和重放网络调用,该中间件提供了一个可以记录和重放请求的 URLSession
实现(使用 Vinyl 或任何其他类似的实现)。 这有助于确保测试在运行之间接收相同的数据,并旨在防止您无法控制的远程服务器上的更改。
在测试中,创建服务器的实例并在 setUp
方法中启动它,并为其提供一个中间件来处理请求。 在 tearDown
方法中停止服务器。
override func setUp() {
server = Server(port: 8080, handler: <#Middleware#>)
try! server.start()
}
override func tearDown() {
try! server.stop()
server = nil
}
中间件是一个函数,它基于传入的请求可以修改响应。
typealias Middleware = (
_ request: Server.HTTPHandler.Request,
_ response: Server.HTTPHandler.Response,
_ next: @escaping () -> Void
) -> Void
当中间件函数完成响应后,它应该调用传递给它的 next
闭包(可以异步调用)以将控制权返回给服务器。 您可以编写自己的中间件,也可以使用 SwiftNIOMock 提供的中间件,包括 redirect
、router
和 delay
。 这是一个简单的中间件示例,它会回显任何传入的请求
func echo(
request: Server.HTTPHandler.Request,
response: Server.HTTPHandler.Response,
next: @escaping () -> Void
) {
response.statusCode = .ok
response.body = request.body
next()
}
redirect
中间件允许您重定向传入的请求并拦截响应。 它还接受 URLSession
的实例(默认情况下为 URLSession.shared
会话),它将使用该实例来执行请求,从而允许它记录和重放所有请求。 查看 SwiftNIOMockExampleUITests.swift
以查看如何在 Vinyl 支持的记录和重放模式下使用 SwiftNIOMock 的示例。
func redirect(
session: URLSession = URLSession.shared,
request override: @escaping (Server.HTTPHandler.Request) -> Server.HTTPHandler.Request,
response intercept: @escaping (Server.HTTPHandler.Response) -> Void = { _ in }
) -> Middleware
router
中间件允许您根据请求(即其方法和路径)返回任意中间件。 如果无法处理请求,则将使用作为 notFound
参数传递的备用中间件。
func router(
route: @escaping (Server.HTTPHandler.Request) -> Middleware?,
notFound: @escaping Middleware
) -> Middleware
要查看在 UI 测试中使用 SwiftNIOMock 的示例,请查看 SwiftNIOMockExample。
虽然您可以使用 router
函数为 Mock 服务器创建路由中间件,但在实践中,最好将其分解为更小的服务。为此,您可以使用 Service
类型
let fooService = Service {
route({ $0.head.uri == "/foo" }) { (request, response, next) in
response.sendString(.ok, value: "Foo")
next()
}
}
let barService = Service {
route({ $0.head.uri == "/bar" }) { (request, response, next) in
response.sendString(.ok, value: "Bar")
next()
}
}
let router = SwiftNIOMock.router(services: [
fooService,
barService
])
您也可以通过继承 Service
来实现您自己的服务
class HelloService: Service {
override init() {
super.init()
routes {
route({ $0.head.uri == "/helloworld" }) { (request, response, next) in
response.sendString(.ok, value: "Hello World!")
next()
}
}
}
}
let helloService = HelloService()
let router = SwiftNIOMock.router(services: [helloService])
您可以像上述示例中那样使用闭包谓词注册路由,也可以使用 URLFormat
import URLFormat
routes {
route(GET/.helloworld) { (request, response, next) in
response.sendString(.ok, value: "Hello World!")
next()
}
}
您还可以使用简写语法将路由绑定到具有 Encodable
返回值类型的服务 KeyPath 或函数
class HelloService: Service {
let users: [String]
func user(byName name: String) -> String? {
users.first { $0 == name }
}
init(users: [String]) {
self.users = users
super.init()
routes {
GET/.users == \HelloService.users
GET/.users/.string == HelloService.user(byName:)
}
}
}
由于路由绑定到键路径或函数,您可以更改服务的状态,这些更改将反映在后面的响应中。
将路由绑定到函数时,其参数类型必须与 URLFormat
类型匹配,即 URLFormat<((String, String), Int)>
只能绑定到函数 (String, String, Int) -> T where T: Encodable
。 此外,您可以添加一个 Server.HTTPHandler.Request
参数作为函数的最后一个参数,以访问请求正文数据。
import PackageDescription
let package = Package(
dependencies: [
.package(url: "https://github.com/ilyapuchka/SwiftNIOMock.git", .branch("master")),
]
)