为使用 URLSession 和 RxSwift 的函数编写单元测试

Write unit test for function that uses URLSession and RxSwift

我有一个函数可以创建 returns Observable,它使用 URLSession 下载和解码数据。我想为此功能编写单元测试,但不知道如何解决它。

函数:

func getRecipes(query: String, _ needsMoreData: Bool) -> Observable<[Recipes]> {

        guard let url = URL(string: "https://api.spoonacular.com/recipes/search?\(query)&apiKey=myApiKey") else {
            return Observable.just([])
        }

        return Observable.create { observer in
            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in

                guard let data = data else {
                    return
                }

                do {
                    if self.recipes == nil {
                        self.recipes = try self.decoder.decode(Recipes.self, from: data)
                        self.dataList = self.recipes.results
                        self.baseUrl = self.recipes.baseUrl
                    } else {
                        if needsMoreData {
                            self.recipes = try self.decoder.decode(Recipes.self, from: data)
                            self.dataList.append(contentsOf: self.recipes.results.suffix(50))
                        } else {
                            self.dataList = try self.decoder.decode(Recipes.self, from: data).results
                        }
                    }
                    observer.onCompleted()
                } catch let error {
                    observer.onError(error)
                }
            }
            task.resume()

            return Disposables.create {
                task.cancel()
            }
        }
        .trackActivity(activityIndicator)
    }

显而易见的答案是注入 dataTask 而不是在函数中使用单例。像这样:

func getRecipes(query: String, _ needsMoreData: Bool, dataTask: @escaping (URL, @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask) -> Observable<[Recipes]> {

    guard let url = URL(string: "https://api.spoonacular.com/recipes/search?\(query)&apiKey=myApiKey") else {
        return Observable.just([])
    }

    return Observable.create { observer in
        let task = dataTask(url) { (data, response, error) in
// and so on...

您可以在主代码中这样调用它:

getRecipes(query: "", false, dataTask: URLSession.shared.dataTask(with:completionHandler:))

在你的测试中,你需要这样的东西:

func fakeDataTask(_ url: URL, _ completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask {
    XCTAssertEqual(url, expectedURL)
    completionHandler(testData, nil, nil)
    return URLSessionDataTask()
}

let result = getRecipes(query: "", false, dataTask: fakeDataTask)

您知道 URLSession 已经为它创建了 Reactive 扩展吗?我最喜欢的是:URLSession.shared.rx.data(request:) 其中 returns 一个 Observable,如果在获取数据时出现任何问题,它会发出错误。我建议你使用它。