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:)
。所以,原来URLSession
的dataTask(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 编译器的错误,与桥接 NSError
和 Error
有关。)
请试试这个:
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)
}
}
}
我想在不调用服务器的情况下测试 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:)
。所以,原来URLSession
的dataTask(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 编译器的错误,与桥接 NSError
和 Error
有关。)
请试试这个:
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)
}
}
}