Last active
October 29, 2015 07:37
-
-
Save rbsgn/b6d72869870bc2969aa6 to your computer and use it in GitHub Desktop.
A minimum viable HTTP response stubber
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
class StubHTTP: NSURLProtocol { | |
// MARK: - Public Interface | |
class func stubRequest(requestTest: NSURLRequest -> Bool, withResponse data: NSData) { | |
stubRequest(requestTest, withResponses: [data]) | |
} | |
class func stubRequest(requestTest: NSURLRequest -> Bool, withResponses datas: [NSData]) { | |
self.stub = Stub(requestTest: requestTest, responses: datas) | |
} | |
class func stubGETRequest(to urlString: String, withString string: String) { | |
stubGETRequestTo(urlString, withResponse: string.dataUsingEncoding(NSUTF8StringEncoding)!) | |
} | |
class func stubGETRequestTo(urlString: String, withResponse data: NSData) { | |
let requestFilter: (NSURLRequest) -> Bool = { request in | |
guard | |
let URL = request.URL, | |
let url = NSURL(string: urlString) | |
else { | |
return false | |
} | |
return url == URL | |
} | |
stubRequest(requestFilter, withResponses: [data]) | |
} | |
class func resetResponseStub() { | |
stub = nil | |
timesResponded = 0 | |
} | |
// MARK: Required methods to override in all NSURLProtocol subclasses | |
static var stub: Stub? | |
override class func canInitWithRequest(request: NSURLRequest) -> Bool { | |
return true | |
} | |
override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest { | |
return request | |
} | |
override func stopLoading() { | |
} | |
override func startLoading() { | |
guard | |
let client = self.client, | |
let stub = self.dynamicType.stub, | |
let requestURL = request.URL | |
else { | |
return | |
} | |
if stub.requestTest(request) { | |
let response = NSHTTPURLResponse(URL: requestURL, statusCode: 0, HTTPVersion: nil, headerFields: nil) | |
client.URLProtocol(self, didReceiveResponse: response!, cacheStoragePolicy: .NotAllowed) | |
client.URLProtocol(self, didLoadData: stub.responses[self.dynamicType.timesResponded++]) | |
client.URLProtocolDidFinishLoading(self) | |
} | |
else { | |
client.URLProtocol(self, didFailWithError: NSError(domain: ErrorDomain, code: Errors.NoStubForRequest.rawValue, userInfo: nil)) | |
} | |
} | |
static var timesResponded = 0 | |
let ErrorDomain = "StubHTTPErrorDomain" | |
enum Errors: Int { | |
case NoStubForRequest = -1 | |
} | |
struct Stub { | |
let requestTest: NSURLRequest -> Bool | |
let responses: [NSData] | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import XCTest | |
class StubHTTPTest: XCTestCase { | |
let stubbedURL = NSURL(string: "http://example.org")! | |
let unstubbedURL = NSURL(string: "http://example.com")! | |
let stubResponse = NSString(string: "foo").dataUsingEncoding(NSUTF8StringEncoding)! | |
let stubResponse2 = NSString(string: "bar").dataUsingEncoding(NSUTF8StringEncoding)! | |
var requestFilter: (NSURLRequest -> Bool)! = nil | |
var waitExpectation: XCTestExpectation! = nil | |
var urlSession: NSURLSession! = nil | |
override func setUp() { | |
super.setUp() | |
requestFilter = { $0.URL == self.stubbedURL } | |
waitExpectation = expectationWithDescription("Wait for stubbed response") | |
let sessionConfiguration = NSURLSessionConfiguration.ephemeralSessionConfiguration() | |
sessionConfiguration.protocolClasses = [ StubHTTP.self ] | |
urlSession = NSURLSession(configuration: sessionConfiguration) | |
} | |
override func tearDown() { | |
StubHTTP.resetResponseStub() | |
super.tearDown() | |
} | |
func test_CanStubHTTPRequestResponse() { | |
StubHTTP.stubRequest(requestFilter, withResponse: stubResponse) | |
let task = urlSession.dataTaskWithURL(stubbedURL) { data, response, error in | |
self.waitExpectation.fulfill() | |
XCTAssertEqual(data, self.stubResponse) | |
} | |
task.resume() | |
waitForExpectationsWithTimeout(0.1, handler: nil) | |
} | |
func test_GivenRequestWithoutStubResponse_FailsWithError() { | |
StubHTTP.stubRequest(requestFilter, withResponse: stubResponse) | |
let task = urlSession.dataTaskWithURL(unstubbedURL) { data, response, error in | |
self.waitExpectation.fulfill() | |
XCTAssertNil(data) | |
XCTAssertNotNil(error) | |
} | |
task.resume() | |
waitForExpectationsWithTimeout(0.1, handler: nil) | |
} | |
func test_CanStubMoreThanOneResponseForRequest() { | |
StubHTTP.stubRequest(requestFilter, withResponses: [stubResponse, stubResponse2]) | |
let task = urlSession.dataTaskWithURL(stubbedURL) { data, response, error in | |
XCTAssertEqual(data, self.stubResponse) | |
let nextTask = self.urlSession.dataTaskWithURL(self.stubbedURL) { data, response, error in | |
XCTAssertEqual(data, self.stubResponse2) | |
self.waitExpectation.fulfill() | |
} | |
nextTask.resume() | |
} | |
task.resume() | |
waitForExpectationsWithTimeout(0.1, handler: nil) | |
} | |
func test_CanStubResponseHTTPRequestResponse_UsingGETRequestShortcut() { | |
StubHTTP.stubGETRequestTo("http://example.com/", withResponse: stubResponse) | |
let task = urlSession.dataTaskWithURL(NSURL(string: "http://example.com/")!) { data, response, error in | |
self.waitExpectation.fulfill() | |
XCTAssertEqual(data, self.stubResponse) | |
} | |
task.resume() | |
waitForExpectationsWithTimeout(0.1, handler: nil) | |
} | |
func test_CanStubGETRequestWithString() { | |
StubHTTP.stubGETRequest(to: "http://example.com/", withString: "foo") | |
makeGETRequest("http://example.com/") { data in | |
self.waitExpectation.fulfill() | |
XCTAssertEqual(data, "foo".dataUsingEncoding(NSUTF8StringEncoding)) | |
} | |
waitForExpectationsWithTimeout(0.1, handler: nil) | |
} | |
func makeGETRequest(urlString: String, completion: (NSData) -> Void) { | |
let task = urlSession.dataTaskWithURL(NSURL(string: urlString)!) { data, _, _ in completion(data!) } | |
task.resume() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment