使用 Swift 结果类型设置 XCTest 时出错
Errors setting up XCTest using Swift Result type
我正在学习如何为我的 API 请求编写测试,但在设置测试的完成代码和响应模型时遇到问题。
我尝试使用 UserResponse 的实例 (let userResponse = UserResponse() ),但是它需要一个值作为它的初始值设定项(来自:Decoder),我不知道里面有什么。我得到的错误是:
"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"
此外,我在创建测试的完成处理程序时遇到错误,我正在使用新的 Swift 结果类型 (Result)。我得到的错误是:
"Type of expression is ambiguous without more context"
这是一个@escaping 函数,但我在测试中收到一条错误消息,要求删除@escaping。
有什么问题吗?我已经在下面用注释标记了故障代码。
谢谢!
// APP CODE
class SignupViewModel: ObservableObject {
func createAccount(user: UserSignup, completion: @escaping( Result<UserResponse, Error>) -> Void) {
AuthService.createAccount(user: user, completion: completion)
}
}
struct UserSignup: Encodable {
var username: String
var email: String
var password: String
}
struct UserResponse: Decodable {
var user: User
var token: String
}
struct User: Decodable {
var username: String
var email: String
// etc
{ private enum UserKeys }
init(from decoder: Decoder) throws { container / decode code }
}
// TEST CODE
class SignupViewModelTests: XCTestCase {
var sut: SignupViewModel!
override func setUpWithError() throws {
sut = SignupViewModel()
}
override func tearDownWithError() throws {
sut = nil
}
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
// WHAT GOES IN from:??
let userResponse = UserResponse(from: Decoder)
// ERROR: "Type of expression is ambiguous without more context"??
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
//arrange
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
sut.createAccount(user: UserSignup, completion: ( Result <UserResponse, Error> ) -> Void ) {
XCTAssertEqual(UserResponse.user.username, "johnsmith")
}
}
}
}
好的,您对代码进行单元测试的方式存在多个问题,我不会详细说明所有问题,但要使它正常工作,您需要的要点是
func testCreateAccount() {
//given
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
let signupExpectation = expectation(description: "Sign up should succeed")
//when
sut.createAccount(user: userSignup) { (result) in
//then
switch result {
case .success(let response):
XCTAssertTrue(response.user.email == "john@test.com", "User email should be john@test.com")
XCTAssertTrue(response.user.username == "johnsmith", "Username should be johnsmith")
XCTAssertTrue(response.token != "", "Token should not be empty")
case .failure(let error):
XCTFail("Authorization should not fail, failed with \(error.localizedDescription)")
}
}
wait(for: [signupExpectation], timeout: 10.0)
}
您正在尝试测试异步 API 调用,因此无法使用您需要 expectation
的同步 XCAssert
语句对其进行测试。如果您使用像 RxTest
这样的第三方库,有一些方法可以使异步 API 调用同步并使用直接的 XCAssert
语句对其进行测试。考虑到您仍然是使用 Swift.
进行单元测试的新手,我认为它超出了您的范围
您可以在这里阅读所有关于期望的信息:Apple doc
我遵循了 Given 的简单代码结构,如答案中的评论所示,如果您不使用任何类型的第三方库来编写描述性单元测试,这是一种非常简洁的代码安排方式喜欢 Quick and Nimble
这里的 raywnderlich 教程中有一篇关于使用普通旧 XCTest 框架进行单元测试的漂亮文章。
最后,作为结束语,我们不会进行实际的 API 调用来在单元测试中测试我们的代码,我们编写伪造和存根来测试我们的代码。
假设您最终为整个项目编写了单元测试,并且您的 CI/CD 系统开始 运行 为所有 PR 和构建启动整个测试套件,并且您的单元测试最终使实际API 调用,进行实际 API 调用所浪费的时间会迅速增加您的测试套件 运行 时间,使发布过程成为一场噩梦。此外,测试后端 API 并不是您单元测试的目的,因此请避免使用 mocks、fakes 和 stubs 进行实际的 API 调用,阅读相关内容 :)
上面提供的单元测试只是告诉你如何使用期望和异步测试API,绝对不会涵盖所有可能的情况:)
要在测试代码中创建 UserResponse
,请调用其合成初始化程序,而不是解码器初始化程序。类似于:
let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
要在测试代码中创建闭包,您需要为其提供代码。测试中的完成闭包有一项工作,即捕获它们是如何被调用的。类似于:
var capturedResponses: [Result<UserResponse, Error>] = []
sut.createAccount(user: UserSignup, completion: { capturedResponses.append([=11=]) })
这会捕获 createAccount(user:completion:)
发送给完成处理程序的 Result
。
...也就是说,看起来您的视图模型正在直接调用进行服务调用的静态函数。如果测试运行,它会在某处创建一个实际帐户吗?或者你有一些我们看不到的边界吗?
而不是直接测试 createAccount(user:completion:)
,您可能想要测试的是:
- 某项操作(注册)将尝试为给定用户创建帐户——但实际上并没有这样做。
- 成功后,视图模型会做一件事。
- 失败时,视图模型会做另一件事。
如果我的假设是正确的,我可以告诉你怎么做。
我正在学习如何为我的 API 请求编写测试,但在设置测试的完成代码和响应模型时遇到问题。
我尝试使用 UserResponse 的实例 (let userResponse = UserResponse() ),但是它需要一个值作为它的初始值设定项(来自:Decoder),我不知道里面有什么。我得到的错误是:
"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"
此外,我在创建测试的完成处理程序时遇到错误,我正在使用新的 Swift 结果类型 (Result
"Type of expression is ambiguous without more context"
这是一个@escaping 函数,但我在测试中收到一条错误消息,要求删除@escaping。
有什么问题吗?我已经在下面用注释标记了故障代码。
谢谢!
// APP CODE
class SignupViewModel: ObservableObject {
func createAccount(user: UserSignup, completion: @escaping( Result<UserResponse, Error>) -> Void) {
AuthService.createAccount(user: user, completion: completion)
}
}
struct UserSignup: Encodable {
var username: String
var email: String
var password: String
}
struct UserResponse: Decodable {
var user: User
var token: String
}
struct User: Decodable {
var username: String
var email: String
// etc
{ private enum UserKeys }
init(from decoder: Decoder) throws { container / decode code }
}
// TEST CODE
class SignupViewModelTests: XCTestCase {
var sut: SignupViewModel!
override func setUpWithError() throws {
sut = SignupViewModel()
}
override func tearDownWithError() throws {
sut = nil
}
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
// WHAT GOES IN from:??
let userResponse = UserResponse(from: Decoder)
// ERROR: "Type of expression is ambiguous without more context"??
func testCreateAccount_WhenGivenSuccessfulResponse_ReturnsSuccess() {
//arrange
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
sut.createAccount(user: UserSignup, completion: ( Result <UserResponse, Error> ) -> Void ) {
XCTAssertEqual(UserResponse.user.username, "johnsmith")
}
}
}
}
好的,您对代码进行单元测试的方式存在多个问题,我不会详细说明所有问题,但要使它正常工作,您需要的要点是
func testCreateAccount() {
//given
let userSignup = UserSignup(username: "johnsmith", email: "john@test.com", password: "abc123abc")
let signupExpectation = expectation(description: "Sign up should succeed")
//when
sut.createAccount(user: userSignup) { (result) in
//then
switch result {
case .success(let response):
XCTAssertTrue(response.user.email == "john@test.com", "User email should be john@test.com")
XCTAssertTrue(response.user.username == "johnsmith", "Username should be johnsmith")
XCTAssertTrue(response.token != "", "Token should not be empty")
case .failure(let error):
XCTFail("Authorization should not fail, failed with \(error.localizedDescription)")
}
}
wait(for: [signupExpectation], timeout: 10.0)
}
您正在尝试测试异步 API 调用,因此无法使用您需要 expectation
的同步 XCAssert
语句对其进行测试。如果您使用像 RxTest
这样的第三方库,有一些方法可以使异步 API 调用同步并使用直接的 XCAssert
语句对其进行测试。考虑到您仍然是使用 Swift.
您可以在这里阅读所有关于期望的信息:Apple doc
我遵循了 Given 的简单代码结构,如答案中的评论所示,如果您不使用任何类型的第三方库来编写描述性单元测试,这是一种非常简洁的代码安排方式喜欢 Quick and Nimble
这里的 raywnderlich 教程中有一篇关于使用普通旧 XCTest 框架进行单元测试的漂亮文章。
最后,作为结束语,我们不会进行实际的 API 调用来在单元测试中测试我们的代码,我们编写伪造和存根来测试我们的代码。
假设您最终为整个项目编写了单元测试,并且您的 CI/CD 系统开始 运行 为所有 PR 和构建启动整个测试套件,并且您的单元测试最终使实际API 调用,进行实际 API 调用所浪费的时间会迅速增加您的测试套件 运行 时间,使发布过程成为一场噩梦。此外,测试后端 API 并不是您单元测试的目的,因此请避免使用 mocks、fakes 和 stubs 进行实际的 API 调用,阅读相关内容 :)
上面提供的单元测试只是告诉你如何使用期望和异步测试API,绝对不会涵盖所有可能的情况:)
要在测试代码中创建 UserResponse
,请调用其合成初始化程序,而不是解码器初始化程序。类似于:
let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
要在测试代码中创建闭包,您需要为其提供代码。测试中的完成闭包有一项工作,即捕获它们是如何被调用的。类似于:
var capturedResponses: [Result<UserResponse, Error>] = []
sut.createAccount(user: UserSignup, completion: { capturedResponses.append([=11=]) })
这会捕获 createAccount(user:completion:)
发送给完成处理程序的 Result
。
...也就是说,看起来您的视图模型正在直接调用进行服务调用的静态函数。如果测试运行,它会在某处创建一个实际帐户吗?或者你有一些我们看不到的边界吗?
而不是直接测试 createAccount(user:completion:)
,您可能想要测试的是:
- 某项操作(注册)将尝试为给定用户创建帐户——但实际上并没有这样做。
- 成功后,视图模型会做一件事。
- 失败时,视图模型会做另一件事。
如果我的假设是正确的,我可以告诉你怎么做。