【问题标题】:iOS swift unit testing - How to test function that makes an api call?iOS swift unit testing - 如何测试调用 api 的函数?
【发布时间】:2021-07-08 00:42:45
【问题描述】:

我正在尝试使用 TDD 创建一个 iOS 应用程序。 我有一个调用 api 的函数。 你能解释一下在这个函数中测试什么吗?

函数获取用户名、密码和闭包作为参数。它应该在收到响应后进行api调用并调用completionHandler。

func login(username: String,
           password: String,
           completionHandler: @escaping (_ result: Result<LoginResponse, Error>) -> Void) {
    // TODO
}

PS:我正在使用 Alamofire 进行网络

【问题讨论】:

  • 您通常会创建一个服务器模拟类,它返回固定的“好”和“坏”服务器响应,并确保它们得到适当的处理。

标签: ios swift unit-testing tdd alamofire


【解决方案1】:

这取决于您要测试的login use case 的哪个“部分”,为您的项目提供一个具体的HTTPClient? (URLSession、Alamofire 等),或者这个服务的实现,但我会给你一些建议。

尝试使用 DI(依赖注入)来准备您的声明(类、结构、函数...),以便使用协议或继承进行测试(在大多数情况下,协议是最佳选择)。然后,评估所有可能的响应情况。我已经为登录服务用例演示做了一个简单的实现。

//
//  Created by Wilmer Barrios.
//

import XCTest

// Business Logic
struct LoginResponse {
    let succeed: Bool
}

protocol LoginService {
    func login(username: String, password: String, completion: @escaping (Result<LoginResponse, Error>) -> Void)
}

// Implementation
class LoginController {
    
    // Presented values
    var presentedSucceedMessage: String?
    var presentedErrorMessage: String?
    
    // Elements (Could be textfields)
    var username: String = ""
    var password: String = ""
    
    private let service: LoginService
    
    init(service: LoginService) {
        self.service = service
    }
    
    func login() {
        service.login(username: username, password: password, completion: { [weak self] result in
            if let response = try? result.get(), response.succeed {
                self?.presentedSucceedMessage = "Login succeed!"
            } else {
                self?.presentedErrorMessage = "Login error!"
            }
        })
    }
}

class LoginControllerTests: XCTestCase {

    func test_init_doesNotLogin() {
        let (_, service) = makeSUT()
        XCTAssertEqual(service.callCount, 0)
    }
    
    func test_login_doesLoadService() {
        let (sut, service) = makeSUT()
        sut.login()
        
        XCTAssertEqual(service.callCount, 1)
    }
    
    func test_loginSucceed_presentsSucceedMessage() {
        let (sut, service) = makeSUT()

        sut.login()
        service.complete(result: .success(makeLoginResponse()))
        
        XCTAssertEqual(sut.presentedSucceedMessage, "Login succeed!")
        XCTAssertNil(sut.presentedErrorMessage)
    }
    
    func test_loginError_presentsErrorMessage() {
        let (sut, service) = makeSUT()

        sut.login()
        service.complete(result: .success(makeLoginResponse(succeed: false)))
        
        XCTAssertEqual(sut.presentedErrorMessage, "Login error!")
        XCTAssertNil(sut.presentedSucceedMessage)
    }
    
    func test_loginFailed_presentsErrorMessage() {
        let (sut, service) = makeSUT()

        sut.login()
        service.complete(result: .failure(makeError()))
        
        XCTAssertEqual(sut.presentedErrorMessage, "Login error!")
        XCTAssertNil(sut.presentedSucceedMessage)
    }
    
    // MARK: Helpers
    private func makeError() -> Error {
        return NSError(domain: "anyError", code: 1)
    }
    
    private func makeLoginResponse(succeed: Bool = true) -> LoginResponse {
        return LoginResponse(succeed: succeed)
    }
    
    private func makeSUT() -> (sut: LoginController, service: LoginServiceMock) {
        let service = LoginServiceMock()
        let sut = LoginController(service: service)
        return (sut, service)
    }
    
    private class LoginServiceMock: LoginService {
        var callCount: Int = 0
        private var completion: ((Result<LoginResponse, Error>) -> Void)?
        
        func login(username: String, password: String, completion: @escaping (Result<LoginResponse, Error>) -> Void) {
            callCount += 1
            self.completion = completion
        }
        
        // Helpers
        func complete(result: Result<LoginResponse, Error>) {
            completion?(result)
        }
    }

}

在这个例子中,您可以请求任何登录服务,可以是您自己的 API、Firebase、任何 SDK,甚至是本地的!您只需使服务客户端符合 LoginService 协议。即AlamoFireLoginClientURLSessionLoginClientFirebaseLoginClient 等。

希望我能帮上忙!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-01
    • 2012-04-06
    • 2020-05-10
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    • 2021-11-27
    相关资源
    最近更新 更多