【问题标题】:Errors setting up XCTest using Swift Result type使用 Swift Result 类型设置 XCTest 时出错
【发布时间】:2021-05-19 11:26:32
【问题描述】:

我正在学习如何为我的 API 请求编写测试,但在设置测试的完成代码和响应模型时遇到了问题。

我尝试使用 UserResponse 的一个实例(让 userResponse = UserResponse() ),但是它需要一个初始化器的值(来自:解码器),我不知道里面有什么。我得到的错误是:

"Argument type 'Decoder.Protocol' does not conform to expected type 'Decoder'"

另外,我在创建测试的完成处理程序时出错,我正在使用新的 Swift Result 类型 (Result)。我得到的错误是:

"Type of expression is ambiguous without more context"

这是一个@escaping 函数,但我收到一条错误消息,提示要在测试中删除@escaping。

关于什么是错的任何想法?下面的故障码我已经用cmets标记了。

谢谢!

// 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")
        }
    }
      
    }
}

【问题讨论】:

    标签: ios swift xcode tdd xctest


    【解决方案1】:

    好的,您对代码进行单元测试的方式存在多个问题,我不会详细介绍所有问题,但您需要的要点是使其正常工作

    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, when 和 then 的简单代码结构,如答案中的注释所示,如果您不使用任何类型的第三方库来编写像 @ 这样的描述性单元测试,这是一种安排代码的非常简洁的方法987654322@和Nimble

    raywnderlich 教程在这里有一篇关于使用普通旧 XCTest 框架进行单元测试的漂亮文章。

    最后,作为结束语,我们不会在单元测试中进行实际的 API 调用来测试我们的代码,我们会编写 fakes 和 stubs 来测试我们的代码。

    假设如果您最终为整个项目编写单元测试,并且您的 CI/CD 系统开始为您的所有 PR 和构建运行整个测试套件,并且您的单元测试最终进行实际的 API 调用,那么这将是浪费在进行实际的 API 调用会迅速增加您的测试套件运行时间,使发布过程成为一场噩梦。测试后端 API 也不是单元测试的目的,因此避免使用 mocks、fakes 和 stub 进行实际 API 调用 :)

    上面提供的单元测试只是为了向您展示如何使用期望和测试异步API,绝对不会涵盖所有可能的情况:)

    【讨论】:

      【解决方案2】:

      要在测试代码中创建UserResponse,请调用其合成初始化程序,而不是解码器初始化程序。比如:

      let userResponse = UserResponse(user: User("Chris"), token: "TOKEN")
      

      要在测试代码中创建闭包,您需要为其提供代码。测试中的完成闭包有一项工作,即捕获它们的调用方式。比如:

      var capturedResponses: [Result<UserResponse, Error>] = []
      sut.createAccount(user: UserSignup, completion: { capturedResponses.append($0) })
      

      这会捕获createAccount(user:completion:) 发送给完成处理程序的Result

      ...也就是说,看起来您的视图模型正在直接调用一个静态函数来进行服务调用。如果测试运行,它会在某处创建一个实际帐户吗?还是你有一些我们看不到的边界?

      您可能想要测试的不是直接测试createAccount(user:completion:),而是:

      • 某个操作(注册)将尝试为给定用户创建帐户,但实际上并没有这样做。
      • 成功后,视图模型将做一件事。
      • 一旦失败,视图模型会做另一件事。

      如果我的假设是正确的,我可以告诉你如何做到这一点。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-11-04
        • 1970-01-01
        • 2016-05-16
        • 2019-09-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-18
        相关资源
        最近更新 更多