dimanche 2 juin 2019

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

I want to test an API without making a server call so I am mocking URLSession and URLSessionDataTask so that I can inject it in my 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)

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

Here when I am running this test it's going in my API class calling the getMovies method. Instance I am getting over there is kind of MockURLSession which is fine. Next moment it gives this ApiTests testGetMoviesSuccessReturnsMovies] : failed: caught "NSInvalidArgumentException", "-[MyAppTests.MockURLSession dataTaskForRequest:completion:]: unrecognized selector sent to instance 0x600003b6d3e0"

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, error) in
            moviesResponse = movies
            moviesExpectation.fulfill()
        }
        waitForExpectations(timeout: 10) { (error) in
            XCTAssertNotNil(moviesResponse)
        }
}


Here is my protocol extension for 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()
    }
}

The reason I think it's giving unrecognised selector is I am not doing configuration part

session = URLSession(configuration: .default)

but I don't know how to set it if I am subclassing and mocking URLSession. Your help will be highly appreciated.

Aucun commentaire:

Enregistrer un commentaire