简体   繁体   中英

iOS swift unit testing - How to test function that makes an api call?

I'm trying to create an iOS application using TDD. I have a function that makes an api call. Could you please explain me what to test in this function?

Function gets username, password and closure as parameters. It should make an api call and call completionHandler upon receiving response.

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

PS: I'm using Alamofire for networking

It depends of what "part" of the login use case you wanted to test, a concrete HTTPClient for your project? (URLSession, Alamofire, etc...), or an implementation of this service, but I'll give you some advices.

Try using DI (Dependency Injection) to prepare your declarations (classes, structs, functions...) for testing with protocols or inheritance (protocols would be the best choice for most cases). Then, evaluate all posible cases that the response can be. I've made a simple implementation for a Login service use case presentation.

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

}

With this example, you could be requesting any login service, it could be your own API, Firebase, any SDK, even a local one,, you just need to make the service client conform the LoginService protocol. ie AlamoFireLoginClient , URLSessionLoginClient , FirebaseLoginClient etc.

I hope I helped!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM