首页 > 解决方案 > 如何快速为方法编写单元测试,返回通过调用模拟类方法获得的响应的承诺?

问题描述

我正在快速为类的方法编写单元测试。这些方法调用为单元测试模拟的类方法。方法返回带有从模拟的静态/类方法获得的响应的承诺。

NetworkAuthProtocol:-

protocol NetworkAuthProtocol {
      static func httpGet(_ url: String, completionHandler complete: @escaping(Any?, Error?) -> Void)
      static func httpDelete(_ url: String, completionHandler complete: @escaping (Any?, Error?) -> Void)
}

下面是使用类方法进行单元测试的模拟类:-

class NetworkAuthMock: NetworkAuthProtocol {

    class func httpGet(_ url: String, completionHandler complete: @escaping(Any?, Error?) -> Void) {
        complete(nil, Error)
    }

    class func httpDelete(_ url: String, completionHandler complete: @escaping (Any?, Error?) -> Void) {
        complete(nil, Error)
    }
}

以下是我正在为其编写单元测试的课程:-

class FetchDataAPIService {

    var networkAuthDelegate: NetworkAuthProtocol.Type = NetworkAuth.self

    func fetchUserData(userUrl: String) -> Promise<Any> {
        return Promise<Any> { seal in
            networkAuthDelegate.httpGet(userUrl) { (result, error) in
                if error == nil {
                    seal.fulfill(result!)
                } else {
                    seal.reject(error!)
                }
            }
        }
    }

   func deleteUserData(userUrl: String) -> Promise<Any> {
        return Promise<Any> { seal in
            networkAuthDelegate.httpDelete(userUrl) { (_, error) in
                if error == nil {
                    seal.fulfill(())
                } else {
                    seal.reject(error!)
                }
            }
        }
    }
}

我编写的单元测试,在下面添加:-

class FetchDataAPIServiceTests: XCTestCase {
    var fetchDataAPIService: FetchDataAPIService!
    var result: String?
    var error: Error?

    override func setUp() {
        super.setUp()
        self.fetchDataAPIService = FetchDataAPIService()
        self.fetchDataAPIService.networkAuthDelegate = NetworkAuthMock.self
        self.error = nil
    }

    override func tearDown() {
        super.tearDown()
    }

    func testFetchUserData() {

        let expectation = XCTestExpectation(description: "test fetchUserData")
        firstly {
            self.fetchDataAPIService.fetchUserData(userUrl: "url")
        }.done { (_) in
            XCTFail("test failed")
        }.catch { (error) in
            XCTAssertNotNil(error)
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 10.0)
    }

    func testDeleteUserData() {

        let expectation = XCTestExpectation(description: "test deleteUserData")
        firstly {
            self.fetchDataAPIService.deleteUserData(userUrl: "url")
        }.done { (_) in
            XCTFail("test failed")
        }.catch { (error) in
            XCTAssertNotNil(error)
            expectation.fulfill()
        }

        wait(for: [expectation], timeout: 10.0)
    }
}

有了期望,测试孤立地通过,但一起失败。我尝试了其他方法,例如在 XCTAssert 语句之外添加 DispatchQueue.main.asyncAfter 。我还在 Mock 类的 httpGet 和 httpDelete 方法中添加了断点,控件永远不会到达那里。似乎没有任何效果。代码覆盖仅在单独运行测试时有效,但有时也会在 main.m 文件中测试构建崩溃。如何使所有测试成功并涵盖所有方法的测试覆盖率?

标签: swiftunit-testing

解决方案


看,这是静态函数的问题之一 - 您需要确保在每次测试之前/之后进行适当的清理,以确保您不会在测试之间造成隐含的依赖关系。

我建议改为使用实例方法切换到更多的 OOP 方法:

protocol NetworkAuthProtocol {

    func httpGet(_ url: String, completionHandler complete: @escaping(Any?, Error?) -> Void)

    func httpDelete(_ url: String, completionHandler complete: @escaping (Any?, Error?) -> Void)
}


class FetchDataAPIService {

    let networkAuthDelegate: NetworkAuthProtocol

    init(networkAuth: NetworkAuthProtocol) {
        self.networkAuthDelegate = networkAuth
    }

,并在您的测试中添加对配置要发送的响应的支持:

class TestsNetworkAuth {
    var httpGetResult: (Any?, Error?) = (nil, nil)
    var httpDeleteResult: (Any?, Error?) = (nil, nil)

    func httpGet(_ url: String, completionHandler complete: @escaping(Any?, Error?) -> Void) {
        complete(httpGetResult.0, httpGetResult.1)
    }

    func httpDelete(_ url: String, completionHandler complete: @escaping (Any?, Error?) -> Void) {
        complete(httpDeleteResult.0, httpDeleteResult.1)
    }
}

class FetchDataAPIServiceTests: XCTestCase {
    var testsNetworkAuth = TestsNetworkAuth()
    var fetchDataAPIService: FetchDataAPIService!

    override func setUp() {
        super.setUp()
        fetchDataAPIService = FetchDataAPIService(networkAuth: testsNetworkAuth)
    }

使用上述方法,您可以添加模拟各种结果数据和/或错误的各种测试。并且测试不会相互干扰,因为每个测试都有自己的TestsNetworkAuth. 由于方法的静态性质,在原始方法中,测试是共享数据的。


推荐阅读