iOS中异步操作的UnitTest

要直接测试网络和异步调用,可以使用XCTest提供的expectationWithDescription+waitForExpectationsWithTimeout,举个例子:

func testFetchDataWithAPI_invalidAPI_failureResult() {
let test_expectation = expectation(description: "")
let timeout = 15 as TimeInterval
networkManager.fetchDataWithAPI(api: DouBanAPI.invalidURL, responseKey: "") {
test_expectation.fulfill()
XCTAssert($0.isFailure)
}
waitForExpectations(timeout: timeout, handler: nil)
}

测试方法按 test方法名测试场景期望结果 的格式命名。首先在异步回调外面调用expectationWithDescription方法得到一个expectation,这个方法接受一个字符串,用来描述本次测试,我传了个空串,因为我们的测试方法名已经足够清晰了。然后在回调中调用expectation.fulfill()表明满足测试条件,接下来就可以进行断言。最后别忘了在回调外面加上waitForExpectationsWithTimeout(timeout, handler: nil),如果时间超过timeout回调还没有执行,就会测试失败,hander会在超时后调用,可以写一些清空状态和还原现场的操作,以免影响之后的测试,譬如task?.cancel()

但是上面这个测试很不稳定,我们可能因为网络不佳测试失败,而不是因为我们的代码本身有逻辑错误,而且这个测试有可能非常慢,而如果我们的网络层是封装的Alamofire的话,其实Alamofire已经帮我们做好了上面的测试了,所以我们只需要测试我们调用Alamofire的方法有没有问题。

比如下面这个方法:

@discardableResult
func fetchDataWithAPI(api: API,
method: Alamofire.HTTPMethod = .get,
responseKey: String,
networkCompletionHandler: @escaping NetworkCompletionHandler) -> Cancellable? {
guard let url = api.url else {
printLog("URL Invalid: \(api)")
return nil
}
return Alamofire.request(url, method: method, parameters: api.parameter).responseJSON {
networkCompletionHandler( self.parseResult(result: $0.result, responseKey: responseKey))
}
}

一般只需要测试它的返回值是否符合预期:

// MARK: - 测试 URL 是否合法的逻辑和调用 Alamofire 的逻辑正确
func testFetchDataWithAPI_invalidAPI_returnNil() {
let task = networkManager.fetchDataWithAPI(api: DouBanAPI.invalidURL, method: .get, responseKey: "", networkCompletionHandler: {_ in })
XCTAssertNil(task)
}
func testFetchDataWithAPI_validAPI_returnNotNil() {
let task = networkManager.fetchDataWithAPI(api: DouBanAPI.song, method: .get, responseKey: "", networkCompletionHandler: { _ in })
XCTAssertNotNil(task)
}

但是因为上面的方法还调用了一个parseResult的方法来处理数据:

func parseResult(result: Alamofire.Result<Any>, responseKey: String) -> Alamofire.Result<Any> {
return result
.flatMap { $0 as? [String: Any] }
.flatMap { $0?.valueFor(key: responseKey) }
.mapError(transform: { (error) -> Error in
return error
})
}

所以我们还需要测试这个方法:

// MARK: - 测试parseResult方法
let testKey = "testKey"
let jsonDictWithError: [String: Any] = ["code": 1]
let jsonDictWithoutData: [String: Any] = ["code": 0]
let jsonDictWithData: [String: Any] = ["testKey": "testValue"]
let error = HTTPError.transformError
func makeResultForFailureCaseWithError(error: Error) -> Result<Any> {
return Result<Any>.failure(error)
}
func makeResultForSuccessCaseWithValue(value: Any) -> Result<Any> {
return Result<Any>.success(value)
}
func testParseResult_failureCase_returnFailureCase() {
let result = makeResultForFailureCaseWithError(error: error)
let formattedResult = networkManager.parseResult(result: result, responseKey: testKey)
XCTAssertTrue(formattedResult.isFailure)
}
func testParseResult_successCaseWithoutData_returnFailureCaseWithTransformFailed() {
let result = makeResultForSuccessCaseWithValue(value: jsonDictWithoutData)
let formattedResult = networkManager.parseResult(result: result, responseKey: testKey)
XCTAssertEqual(formattedResult.error as? HTTPError, HTTPError.transformError)
}
func testParseResult_successCaseWithData_returnSuccess() {
let result = makeResultForSuccessCaseWithValue(value: jsonDictWithData)
let formattedResult = networkManager.parseResult(result: result, responseKey: testKey)
XCTAssertEqual(formattedResult.value as? String, "testValue")
}

这个测试也是测试返回值,测试了几种可能发生的情况,基本可以保证parseResult方法的正确性。

这样一个简单的网络层的单元测试就写好了。

关于封装的网络层地址: SwiftDemo, 里边还有一个简单的Swift初始工程