Write unit test for function that uses URLSession and RxSwift

Viewed 573

I have a function that creates and returns Observable that downloads and decodes data using URLSession. I wanted to write unit test for this function but have no idea how to tackle it.

function:

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)
    }
1 Answers

The obvious answer is to inject the dataTask instead of using the singleton inside your function. Something like this:

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...

You would call it in the main code like this:

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

In your test, you would need something like this:

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)

Did you know that URLSession has Reactive extensions already created for it? The one I like best is: URLSession.shared.rx.data(request:) which returns an Observable which will emit an error if there are any problems getting the data. I suggest you use it.

Related