Skip to content

Instantly share code, notes, and snippets.

@matsuda
Last active March 22, 2018 06:43
Show Gist options
  • Save matsuda/e77c93dc0aeb093bcd8882af1eec4433 to your computer and use it in GitHub Desktop.
Save matsuda/e77c93dc0aeb093bcd8882af1eec4433 to your computer and use it in GitHub Desktop.
XCTestにおけるスタブ(仮)
// テストターゲット=`SampleAppTest`
// ホストアプリ=`SampleApp`
import Foundation
import Alamofire
struct APIStub {
let statusCode: Int
let result: Result<Data>?
var data: Data? {
switch result {
case .some(.success(let data)):
return data
default:
return nil
}
}
var error: Error? {
switch result {
case .some(.failure(let error)):
return error
default:
return nil
}
}
}
class APIStubProtocol: URLProtocol {
private static var stub: APIStub?
override class func canInit(with request: URLRequest) -> Bool {
if stub == nil { return false }
return true
}
override class func canonicalRequest(for request: URLRequest) -> URLRequest {
return request
}
override func startLoading() {
defer {
APIStubProtocol.resetStub()
}
if let stub = APIStubProtocol.stub, let data = stub.data, (200..<400).contains(stub.statusCode) {
let response = HTTPURLResponse(url: request.url!, statusCode: stub.statusCode, httpVersion: "HTTP/1.1", headerFields: [:])
client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didLoad: data)
client?.urlProtocolDidFinishLoading(self)
return
}
var statusCode: Int = 400
var error: Error = NSError(domain: "SampleAppTest", code: 10001, userInfo: nil)
if let stub = APIStubProtocol.stub {
statusCode = stub.statusCode
if let e = stub.error {
error = e
}
}
let response = HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: "HTTP/1.1", headerFields: [:])
client?.urlProtocol(self, didReceive: response!, cacheStoragePolicy: .notAllowed)
client?.urlProtocol(self, didFailWithError: error)
}
override func stopLoading() {
APIStubProtocol.resetStub()
}
class func addStub(_ stub: APIStub) {
self.stub = stub
}
class func resetStub() {
self.stub = nil
}
}
extension URLSessionConfiguration {
@objc class func swizzlingAPIStub() {
let defaultConfig = class_getClassMethod(URLSessionConfiguration.self, #selector(getter:URLSessionConfiguration.default))
let stubbedConfig = class_getClassMethod(URLSessionConfiguration.self, #selector(URLSessionConfiguration.stubbedDefaultConfiguration))
method_exchangeImplementations(defaultConfig!, stubbedConfig!)
}
@objc class func stubbedDefaultConfiguration() -> URLSessionConfiguration {
let config = stubbedDefaultConfiguration()
config.protocolClasses = [APIStubProtocol.self] as [AnyClass] + config.protocolClasses!
return config
}
}
// テストターゲット=`SampleAppTest`
// ホストアプリ=`SampleApp`
import XCTest
@testable import SampleApp
class ArticleAPITests: BaseTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
APIStubProtocol.resetStub()
}
func testStub() {
// Given
let expectation = XCTestExpectation(description: "request should succeed")
APIStubProtocol.addStub(APIStub(statusCode: 200, result: stubResult(forResource: "articles", withExtension: "json")))
// When
ArticleAPI().get { (result) in
// Then
XCTAssertNotNil(result)
XCTAssert(result?.isSuccess == true, "response should be success")
XCTAssertNil(result?.errorCode)
XCTAssertNotNil(result?.data)
XCTAssertEqual(result?.data?.first?.id, 123)
expectation.fulfill()
}
wait(for: [expectation], timeout: 1.0)
}
}
// テストターゲット=`SampleAppTest`
// ホストアプリ=`SampleApp`
import XCTest
import Alamofire
class BaseTestCase: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
func stubURL(forResource fileName: String, withExtension ext: String = "json") -> URL {
return Bundle(for: BaseTestCase.self).url(forResource: fileName, withExtension: ext)!
}
func stubData(forResource fileName: String, withExtension ext: String = "json") -> Data {
return try! Data(contentsOf: stubURL(forResource: fileName, withExtension: ext))
}
func stubResult(forResource fileName: String, withExtension ext: String = "json") -> Result<Data> {
return Result<Data>.success(stubData(forResource: fileName, withExtension: ext))
}
}
// テストターゲット=`SampleAppTest`
// ホストアプリ=`SampleApp`
#import <Foundation/Foundation.h>
#import <SampleAppTest-Swift.h>
@interface StubLoader: NSObject
@end
@implementation StubLoader
+ (void)load {
[NSURLSessionConfiguration swizzlingAPIStub];
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment