API 测试的模拟 URLSession 正在将无法识别的选择器发送到实例

Mocking URLSession for API Testing is giving unrecognized selector sent to instance

我想在不调用服务器的情况下测试 API,所以我正在模拟 URLSession 和 URLSessionDataTask,以便我可以将它注入我的 API class.

 class MockURLSession: URLSession {
    private let mockTask: MockTask
    var cachedUrl: URL?


    init(data: Data?, urlResponse: URLResponse?, error: Error?) {
        mockTask = MockTask(data: data, urlResponse: urlResponse, error:
            error)
    }

    override func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {

        self.cachedUrl = url
        mockTask.completionHandler = completionHandler
        return mockTask
    }
}

class MockTask: URLSessionDataTask {
    private let data: Data?
    private let urlResponse: URLResponse?
    var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!

    init(data: Data?, urlResponse: URLResponse?, error: Error?) {
        self.data = data
        self.urlResponse = urlResponse
    }

    override func resume() {
        DispatchQueue.main.async {
            self.completionHandler(self.data, self.urlResponse, self.error)
        }
    }
}

这里,当我 运行 进行此测试时,它会在我的 API class 中调用 getMovies 方法。实例我正在那边有一种 MockURLSession 很好。下一刻它给出了这个 ApiTests testGetMoviesSuccessReturnsMovies] : failed: caught "NSInvalidArgumentException", "-[MyAppTests.MockTask error]: unrecognized selector sent to instance 0x600002010500

func testGetMoviesSuccessReturnsMovies() {
        let jsonData = "[{\"title\": \"Spider Man Far From Home\",\"detail\": \"The first Spider-Man featuring Tom Holland in the iconic role\"}]".data(using: .utf8)
        var mockURLSession  = MockURLSession(data: jsonData, urlResponse: nil, error: nil)
        let apiRespository = APIRepository(session: mockURLSession)
        let moviesExpectation = expectation(description: "movies")
        var moviesResponse: Result<[Movie]>?

        apiRespository.getMovies { (movies) in
            moviesResponse = movies
            moviesExpectation.fulfill()
        }
        waitForExpectations(timeout: 10) { (error) in
            XCTAssertNotNil(moviesResponse)
        }
}

这是我对 api

的协议扩展
extension Gettable {

    func get<T:Decodable>(with decodingType: T.Type, url: String, session: URLSession, completion:@escaping(Result<T>) -> Void) {

        let dataTask = session.dataTask(with: URL(string: url)!) { (data, response, error) in
             guard data != nil && error == nil  else {
                return
            }
            do {
                let decoder = JSONDecoder()
                let parsedObj = try decoder.decode(T.self, from: data ?? Data())
                completion(Result.success(parsedObj))
            }
            catch let parsedError {
                completion(Result.failure(parsedError))
            }
        }
        dataTask.resume()
    }
}

非常感谢您的帮助。

您没有覆盖 MockURLSession 中的方法 dataTask(with:completionHandler:)。所以,原来URLSessiondataTask(with:completionHandler:)被调用了,内部调用了dataTaskForRequest:completion:.

如果您成功覆盖了父 class 中的现有方法,Swift 编译器声明要为 override 关键字添加前缀。

将嵌套的 dataTask(with:completionHandler:) 移出 init(data:urlResponse:error:)

init(data: Data?, urlResponse: URLResponse?, error: Error?) {
    mockTask = MockTask(data: data, urlResponse: urlResponse, error:
        error)
}

override func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {

    self.cachedUrl = url
    mockTask.completionHandler = completionHandler
    return mockTask
}

编辑部分...

error属性 的继承似乎无法正常工作。 (这可能是 Swift 编译器的错误,与桥接 NSErrorError 有关。)

请试试这个:

class MockTask: URLSessionDataTask {
    private let data: Data?
    private let urlResponse: URLResponse?

    private let _error: Error?
    override var error: Error? {
        return _error
    }

    var completionHandler: ((Data?, URLResponse?, Error?) -> Void)!

    init(data: Data?, urlResponse: URLResponse?, error: Error?) {
        self.data = data
        self.urlResponse = urlResponse
        self._error = error
    }

    override func resume() {
        DispatchQueue.main.async {
            self.completionHandler(self.data, self.urlResponse, self.error)
        }
    }
}