震惊
一个用 Swift 编写的 HTTP 模拟框架。
摘要
-
😎 无痛点 API 模拟:Shock 允许您快速轻松地为应用发出的网络请求提供模拟响应。 -
🧪 隔离模拟:当与 UI 测试一起使用时,Shock 在 UI 测试过程中运行其服务器,并在 UI 测试目标中存储所有响应 - 因此不需要在应用程序目标中填充大量测试数据和逻辑。 -
⭐️ 震惊现在支持并行 UI 测试!:Shock 可以在并行测试过程中运行隔离服务器。有关更多详细信息,请参阅以下内容! -
🔌 震惊现在可以托管基本套接字:除了 HTTP 服务器之外,Shock 还可以托管套接字服务器,以执行各种测试任务。有关更多详细信息,请参阅以下内容!
安装
CocoaPods
将以下内容添加到您的Podfile中
pod 'Shock', '~> x.y.z'
您可以在cocoapods.org找到最新版本
SPM
复制此仓库的URL,并在项目设置中添加此包。
模拟HTTP请求
Shock旨在提供一个简单的接口来设置模拟。
请看以下示例
class HappyPathTests: XCTestCase {
var mockServer: MockServer!
override func setUp() {
super.setUp()
mockServer = MockServer(port: 6789, bundle: Bundle.module)
mockServer.start()
}
override func tearDown() {
mockServer.stop()
super.tearDown()
}
func testExample() {
let route: MockHTTPRoute = .simple(
method: .get,
urlPath: "/my/api/endpoint",
code: 200,
filename: "my-test-data.json"
)
mockServer.setup(route: route)
/* ... Your test code ... */
}
}
请注意,在测试运行期间,您需要将API端点主机名替换为'localhost',并在设置方法中指定的端口号。
例如 https://:{PORT}/my/api/endpoint
在UI测试的情况下,最快速的方式是通过向应用程序传递一个启动参数来指示要使用哪个端点。例如
let args = ProcessInfo.processInfo.arguments
let isRunningUITests = args.contains("UITests")
let port = args["MockServerPort"]
if isRunningUITests {
apiConfiguration.setHostname("https://:\(port)/")
}
注意
路由类型
Shock为不同的情况提供了不同类型的模拟路由。所有路由都遵循Codable协议,可以从JSON文件中解码。
简单路由
简单的模拟是定义模拟路由的首选方法。它响应测试包中JSON文件的内容,提供的文件名如下所示用于模拟声明
let route: MockHTTPRoute = .simple(
method: .get,
urlPath: "/my/api/endpoint",
code: 200,
filename: "my-test-data.json"
)
JSON
{
"type": "simple",
"method": "GET",
"urlPath": "/my/api/endpoint",
"code": 200,
"filename" : "my-test-data.json"
}
自定义路由
自定义模拟功能可以使您进一步定制路由定义,包括添加查询字符串参数和HTTP头。
这使您能够更精确地控制您的模拟需要处理的请求的更详细的部分。
自定义路由会尽量严格地匹配您的查询和头定义,因此请确保您为所有这些值的变体添加自定义路由。
let route = MockHTTPRoute = .custom(
method: .get,
urlPath: "/my/api/endpoint",
query: ["queryKey": "queryValue"],
requestHeaders: ["X-Custom-Header": "custom-header-value"],
responseHeaders: ["Content-Type": "application/json"],
code: 200,
filename: "my-test-data.json"
)
JSON
{
"type": "custom",
"method": "GET",
"urlPath": "/my/api/endpoint",
"query": {
"queryKey": "queryValue"
},
"requestHeaders": {
"X-Custom-Header": "custom-header-value"
},
"responseHeaders": {
"Content-Type": "application/json"
},
"code": 200,
"filename": "my-test-data.json"
}
重定向路由
有时我们只是希望我们的模拟重定向到另一个URL。重定向模拟允许您返回到另一个URL或端点的301重定向。
let route: MockHTTPRoute = .redirect(urlPath: "/source", destination: "/destination")
JSON
{
"type": "redirect",
"urlPath": "/source",
"destination": "/destination"
}
模板路由
模板模拟允许您在运行时构建请求的模拟响应。它使用Mustache来允许在您设置模拟时在您的响应中构建值。
例如,您可能想在一个响应中包含一个根据测试需求可变大小的项目数组。
查看Shock Route Tester示例应用中的/template
路由以获取更详细的示例。
let route = MockHTTPRoute = .template(
method: .get,
urlPath: "/template",
code: 200,
filename: "my-templated-data.json",
templateInfo: [
"list": ["Item #1", "Item #2"],
"text": "text"
])
)
JSON
{
"type": "template",
"method": "GET",
"urlPath": "/template",
"code": 200,
"filename": "my-templated-data.json",
"templateInfo": {
"list": ["Item #1", "Item #2"],
"text": "text"
}
}
集合
集合路由包含其他模拟路由的数组。它是一个用于存储和组织不同测试的路由的容器。一般来说,如果您的测试使用多个路由
集合路由是递归添加的,因此给定的集合路由可以安全地包含在另一个集合路由中。
let firstRoute: MockHTTPRoute = .simple(method: .get, urlPath: "/route1", code: 200, filename: "data1.json")
let secondRoute: MockHTTPRoute = .simple(method: .get, urlPath: "/route2", code: 200, filename: "data2.json")
let collectionRoute: MockHTTPRoute = .collection(routes: [ firstRoute, secondRoute ])
JSON
{
"type": "collection",
"routes": [
{
"type": "simple",
"method": "GET",
"urlPath": "/my/api/endpoint",
"code": 200,
"filename" : "my-test-data.json"
},
{
"type": "simple",
"method": "GET",
"urlPath": "/my/api/endpoint2",
"code": 200,
"filename" : "my-test-data2.json"
}
]
}
超时路由
超时路由用于测试客户端超时代码路径。它简单地在可配置的秒数内等待(默认为120秒)。注意如果您指定了自定义的超时时间,请确保它超过了您客户端的超时设置。
let route: MockHTTPRoute = .timeout(method: .get, urlPath: "/timeouttest")
let route: MockHTTPRoute = .timeout(method: .get, urlPath: "/timeouttest", timeoutInSeconds: 5)
JSON
{
"type": "timeout",
"method": "GET",
"urlPath": "/timeouttest",
"timeoutInSeconds": 5
}
强制所有调用都需要模拟
在某些情况下,您可能希望所有调用都进行模拟,以便测试可以无需互联网连接即可可靠运行。您可以通过以下方式强制这种行为
server.shouldSendNotFoundForMissingRoutes = true
这将为任何未识别的路径发送一个带有空响应体的404状态码。
中间件
Shock 现在支持中间件!中间件允许您使用自定义逻辑来处理给定的请求。
🤝 中间件可以与或无需模拟路由使用。⛓ 中间件是可连锁的,第一个中间件接收上下文,并将其传递给下一个,依此类推
ClosureMiddleware
使用中间件最简单的方法是将ClosureMiddleware的实例添加到服务器中。例如
let myMiddleware = ClosureMiddleware { request, response, next in
if request.headers["X-Question"] == "Can I have a cup of tea?" {
response.headers["X-Answer"] = "Yes, you can!"
}
next()
}
mockServer.add(middleware: myMiddleware)
上述代码将查找名为X-Question
的请求头,如果存在且具有预期的值,它将在'response header'中发送一个答案。
使用模拟路由和中间件协同工作
模拟路由和中间件可以很好地协同工作,但有几个值得注意的地方
- 模拟路由由一个中间件管理
- 当向服务器添加第一个模拟路由时,此中间件将添加到现有中间件堆栈中。
对于像上述例子那样的中间件,中间件的顺序不重要。但是,如果您正在更改由模拟路由中间件设置的响应的某个部分,您可能会得到意外结果!
Socket Server
Shock现在可以运行HTTP服务器以外的socket服务器。这在需要模拟HTTP请求和socket服务器的情况下很有用。Socket服务器的术语与HTTP服务器相似,因此它继承了“路由”这个术语来指代一种socket数据处理类型。API与HTTP API类似,你需要创建一个MockServerRoute
,使用包含路线的setupSocket
调用,当服务器调用start
时,将会使用您的路线设置socket(假设已注册至少一个路线)。
如果没有设置MockServerRoute
,则不会启动socket服务器。
先决条件
因为Socket服务器只能在HTTP服务器之外运行,所以Shock需要至少两个端口号的范围,使用接受范围作为init
方法的参数。
let range: ClosedRange<Int> = 10000...10010
let server = MockServer(portRange: range, bundle: ...)
可用路由
Socket服务器目前只有一个可用的路由,即logStashEcho
。此路由将设置一个socket,它接受将消息记录到Logstash并作为字符串回显这些消息。
以下是一个使用logStashEcho
与我们的JustLog框架的示例。
import JustLog
import Shock
let server = MockServer(portRange: 9090...9099, bundle: ...)
let route = MockSocketRoute.logStashEcho { (log) in
print("Received \(log)"
}
server.setupSocket(route: route)
server.start()
let logger = Logger.shared
logger.logstashHost = "localhost"
logger.logstashPort = UInt16(server.selectedSocketPort)
logger.enableLogstashLogging = true
logger.allowUntrustedServer = true
logger.setup()
logger.info("Hello world!")
需要注意的是,Shock是一个不受信任的服务器,因此需要设置logger.allowUntrustedServer = true
。
Shock路由测试器
Shock路由测试器示例应用程序允许您尝试不同的路由类型。编辑MyRoutes.swift
文件以添加您自己的,并在应用程序中测试它们。
许可证
震惊库可在Apache License 2.0下使用。有关更多信息,请参阅LICENSE文件。