如果未调用闭包,我如何使 XCTest 失败?

How can I fail an XCTest if a closure isn't invoked?

我有以下单元测试:

let apiService = FreeApiService(httpClient: httpClient)
apiService.connect() {(status, error) in
     XCTAssertTrue(status)
     XCTAssertNil(error)
}

实际函数如下所示:

typealias freeConnectCompleteClosure = ( _ status: Bool, _ error: ApiServiceError?)->Void

class FreeApiService : FreeApiServiceProtocol {
    func connect(complete: @escaping freeConnectCompleteClosure) {
       ...
       case 200:
            print("200 OK")
            complete(true, nil)
    }
}

这个单元测试通过了,但问题是,如果我忘记了 complete(true, nil) 部分,测试仍然会通过。如果没有调用闭包,我找不到让我的测试默认失败的方法。

我错过了什么?

为此您需要使用 XCTTestExpectation。除非明确满足,否则将无法通过测试。

通过调用 expectation(description:) or one of the related methods. Inside the callback in your test, invoke fulfill() on the expectation. Finally, after your call to connect(), call one of XCTestCase's "wait" methods, such as waitForExpectations(timeout:) 创建期望作为测试方法设置的一部分。如果您忘记在您的应用程序代码中调用回调,超时将会过去,您将不再有误报。

Apple 的 "Testing Asynchronous Operations with Expectations" 文档中提供了完整示例。

您似乎正在尝试针对 FreeApiService 编写测试。但是,从名称(和完成处理程序)来看,FreeApiService 进行了网络调用。这是在单元测试中要避免的事情,因为测试不再仅仅依赖于你的代码。这取决于

  • 拥有可靠的互联网连接
  • 后端启动中
  • 后端在给定之前响应time-out
  • 后端发出预期响应

如果您愿意忍受不稳定(由于上述依赖性)和缓慢(由于网络延迟)的测试,那么您可以编写异步测试。它看起来像这样:

func testConnect_ShouldCallCompletionHandlerWithTrueStatusAndNilError() {
    let apiService = FreeApiService(httpClient: httpClient)
    var capturedStatus: Bool?
    var capturedError: Error?

    let promise = expectation(description: "Completion handler invoked")
    apiService.connect() {(status, error) in
        capturedStatus = status
        capturedError = error
        promise.fulfill()
    }
    waitForExpectations(timeout: 5, handler: nil)

    XCTAssertTrue(capturedStatus ?? false, "status")
    XCTAssertNil(capturedError, "error")
}

如果未调用完成处理程序,waitForExpectations 将在 5 秒后超时。测试将因此失败。

我避免将断言放入完成处理程序中。相反,正如我在 A Design Pattern for Tests that Do Real Networking 中描述的那样,我建议:

  • 捕获我们要测试的参数
  • 触发转义标志

然后可以在完成处理程序之外执行所有断言。

……但是!我尝试为 back-end 系统本身的验收测试保留异步测试。如果您想测试自己的代码,异步测试的混乱性质表明被测代码的设计需要改进。网络调用形成了清晰的边界。我们可以测试到那个边界。我们可以测试任何被扔回给我们的东西,但在我们这边的边界上。也就是说,我们可以测试:

  • 网络请求格式是否正确?但不要发出请求。
  • 我们是否正确处理网络响应?但是模拟响应。

重塑代码以允许这些测试将使我们能够编写快速且确定的测试。他们甚至不需要互联网连接。