【问题标题】:query regarding mocking singleton in swift ,ios using xctest?关于使用 xctest 在 swift、ios 中模拟单例的查询?
【发布时间】:2019-12-17 16:18:05
【问题描述】:

这不是关于我们是否应该使用单例的问题。而是嘲笑单例相关。 这只是一个示例,因为我正在阅读有关嘲笑单身人士的文章。所以我想让我试一试。 我可以模拟它,但不确定这是正确的方法吗?

protocol APIManagerProtocol {
    static var sharedManager: APIManagerProtocol {get set}
    func doThis()

}

class APIManager: APIManagerProtocol {
    static var sharedManager: APIManagerProtocol = APIManager()
    private init() {
    }

    func doThis() {

    }
}

class ViewController: UIViewController {

    private var apiManager: APIManagerProtocol?
    override func viewDidLoad() {

    }
    convenience init(_ apimanager: APIManagerProtocol){
        self.init()
        apiManager = apimanager
    }

    func DoSomeRandomStuff(){
        apiManager?.doThis()
    }
}

import Foundation
@testable import SingleTonUnitTesting

class MockAPIManager: APIManagerProtocol {
    static var sharedManager: APIManagerProtocol = MockAPIManager()

    var isdoThisCalled = false

    func doThis(){
        isdoThisCalled = true
    }
    private init(){

    }
}

class ViewControllerTests: XCTestCase {

    var sut: ViewController?
    var mockAPIManager: MockAPIManager?

    override func setUp() {
        mockAPIManager = MockAPIManager.sharedManager as? MockAPIManager
        sut = ViewController(mockAPIManager!)
    }


    func test_viewController_doSomeRandomStuffs(){
        sut?.DoSomeRandomStuff()
        XCTAssertTrue(mockAPIManager!.isdoThisCalled)
    }

    override func tearDown() {
        sut = nil
        mockAPIManager = nil
    }


}

【问题讨论】:

    标签: ios swift unit-testing


    【解决方案1】:

    基本思路是对的:避免在整个代码中直接重复引用单例,而是注入符合协议的对象。

    不太正确的是,您正在测试 MockAPIManager 类的内部内容。模拟只是为了服务于更广泛的目标,即测试您的业务逻辑(没有外部依赖项)。因此,理想情况下,您应该测试由APIManagerProtocol 公开的内容(或某些逻辑结果)。

    所以,让我们具体说明一下:例如,假设您的 API 有一些方法可以从 Web 服务中检索用户的年龄:

    public protocol APIManagerProtocol {
        func fetchAge(for userid: String, completion: @escaping (Result<Int, Error>) -> Void)
    }
    

    (注意,顺便说一下,static 单例方法不属于协议。它是 API 管理器的实现细节,而不是协议的一部分。注入管理器的控制器永远不需要自己打电话给shared/sharedManager。)

    假设您的视图控制器(或者更好的是,它的视图模型/演示者)具有检索年龄并创建要在 UI 中显示的适当消息的方法:

    func buildAgeMessage(for userid: String, completion: @escaping (String) -> Void) {
        apiManager?.fetchAge(for: userid) { result in
            switch result {
            case .failure:
                completion("Error retrieving age.")
    
            case .success(let age):
                completion("The user is \(age) years old.")
            }
        }
    }
    

    API 管理器模拟然后将实现该方法:

    class MockAPIManager: APIManagerProtocol {
        func fetchAge(for userid: String, completion: @escaping (Result<Int, Error>) -> Void) {
            switch userid {
            case "123":
                completion(.success(42))
    
            default:
                completion(.failure(APIManagerError.notFound))
            }
        }
    }
    

    然后您可以使用模拟 API 而不是实际的网络服务来测试构建此字符串以显示在您的 UI 中的逻辑:

    class ViewControllerTests: XCTestCase {
        var viewController: ViewController?
    
        override func setUp() {
            viewController = ViewController(MockAPIManager())
        }
    
        func testSuccessfulAgeMessage() {
            let e = expectation(description: "testSuccessfulAgeMessage")
            viewController?.buildAgeMessage(for: "123") { string in
                XCTAssertEqual(string, "The user is 42 years old.")
                e.fulfill()
            }
            waitForExpectations(timeout: 1)
        }
    
        func testFailureAgeMessage() {
            let e = expectation(description: "testFailureAgeMessage")
            viewController?.buildAgeMessage(for: "xyz") { string in
                XCTAssertEqual(string, "Error retrieving age.")
                e.fulfill()
            }
            waitForExpectations(timeout: 1)
        }
    }
    

    我正在阅读关于嘲笑单身人士很难

    这个概念是,如果您将这些 APIManager.shared 引用散布在您的代码中,则更难将它们与模拟对象交换出来。注入解决了这个问题。

    然后,再一次,如果您现在已在各处注入此 APIManager 实例以方便模拟并消除所有这些 shared 引用,那么您想避免的问题是,为什么要再使用单例?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-07
      • 1970-01-01
      • 2013-11-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多