-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support UI Testing #55
base: master
Are you sure you want to change the base?
Changes from 4 commits
ded5c19
5357aea
b8c3cbe
7cc9710
a4523a8
0002750
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,12 +5,10 @@ public class Session: NSURLSession { | |
// MARK: - Properties | ||
|
||
public var outputDirectory: String | ||
public let cassetteName: String | ||
public var cassetteURL: NSURL? | ||
public let backingSession: NSURLSession | ||
public var recordingEnabled = true | ||
|
||
private let testBundle: NSBundle | ||
|
||
private var recording = false | ||
private var needsPersistence = false | ||
private var outstandingTasks = [NSURLSessionTask]() | ||
|
@@ -23,10 +21,14 @@ public class Session: NSURLSession { | |
|
||
// MARK: - Initializers | ||
|
||
public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first!, backingSession: NSURLSession = NSURLSession.sharedSession()) { | ||
convenience public init(outputDirectory: String = "~/Desktop/DVR/", cassetteName: String, testBundle: NSBundle? = NSBundle.allBundles().filter() { $0.bundlePath.hasSuffix(".xctest") }.first, backingSession: NSURLSession = NSURLSession.sharedSession()) { | ||
let bundle = testBundle ?? NSBundle.mainBundle() | ||
self.init(outputDirectory: outputDirectory, cassetteURL: bundle.URLForResource(cassetteName, withExtension: "json"), backingSession: backingSession) | ||
} | ||
|
||
public init(outputDirectory: String = "~/Desktop/DVR/", cassetteURL: NSURL?, backingSession: NSURLSession = NSURLSession.sharedSession()) { | ||
self.outputDirectory = outputDirectory | ||
self.cassetteName = cassetteName | ||
self.testBundle = testBundle | ||
self.cassetteURL = cassetteURL | ||
self.backingSession = backingSession | ||
super.init() | ||
} | ||
|
@@ -64,7 +66,7 @@ public class Session: NSURLSession { | |
} | ||
|
||
public override func uploadTaskWithRequest(request: NSURLRequest, fromFile fileURL: NSURL, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionUploadTask { | ||
let data = NSData(contentsOfURL: fileURL)! | ||
let data = NSData(contentsOfURL: fileURL) | ||
return addUploadTask(request, fromData: data, completionHandler: completionHandler) | ||
} | ||
|
||
|
@@ -110,8 +112,8 @@ public class Session: NSURLSession { | |
// MARK: - Internal | ||
|
||
var cassette: Cassette? { | ||
guard let path = testBundle.pathForResource(cassetteName, ofType: "json"), | ||
data = NSData(contentsOfFile: path), | ||
guard let cassetteURL = cassetteURL, | ||
data = NSData(contentsOfURL: cassetteURL), | ||
raw = try? NSJSONSerialization.JSONObjectWithData(data, options: []), | ||
json = raw as? [String: AnyObject] | ||
else { return nil } | ||
|
@@ -195,6 +197,10 @@ public class Session: NSURLSession { | |
} | ||
} | ||
|
||
var cassetteName = "cassette" | ||
if let s = cassetteURL?.lastPathComponent { | ||
cassetteName = s.substringToIndex(s.endIndex.advancedBy(-5)) | ||
} | ||
let cassette = Cassette(name: cassetteName, interactions: interactions) | ||
|
||
// Persist | ||
|
@@ -214,9 +220,9 @@ public class Session: NSURLSession { | |
if let data = string.dataUsingEncoding(NSUTF8StringEncoding) { | ||
data.writeToFile(outputPath, atomically: true) | ||
print("[DVR] Persisted cassette at \(outputPath). Please add this file to your test target") | ||
} else { | ||
print("[DVR] Failed to persist cassette.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my PR to convert to Swift, I added a |
||
} | ||
|
||
print("[DVR] Failed to persist cassette.") | ||
} catch { | ||
print("[DVR] Failed to persist cassette.") | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,27 +12,35 @@ class SessionUploadTests: XCTestCase { | |
return request | ||
}() | ||
let multipartBoundary = "---------------------------3klfenalksjflkjoi9auf89eshajsnl3kjnwal".UTF8Data() | ||
lazy var testFile: NSURL = { | ||
return NSBundle(forClass: self.dynamicType).URLForResource("testfile", withExtension: "txt")! | ||
lazy var testFile: NSURL? = { | ||
return NSBundle(forClass: self.dynamicType).URLForResource("testfile", withExtension: "txt") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👎 In tests, it's better to ! things so you can see the error right away durning the test. |
||
}() | ||
|
||
func testUploadFile() { | ||
let session = Session(cassetteName: "upload-file") | ||
session.recordingEnabled = false | ||
let expectation = expectationWithDescription("Network") | ||
|
||
let data = encodeMultipartBody(NSData(contentsOfURL: testFile)!, parameters: [:]) | ||
guard let testFile = testFile else { XCTFail("Missing test file URL"); return } | ||
guard let fileData = NSData(contentsOfURL: testFile) else { XCTFail("Missing body data"); return } | ||
let data = encodeMultipartBody(fileData, parameters: [:]) | ||
let file = writeDataToFile(data, fileName: "upload-file") | ||
|
||
session.uploadTaskWithRequest(request, fromFile: file) { data, response, error in | ||
if let error = error { | ||
XCTFail("Error uploading file: \(error)") | ||
return | ||
} | ||
guard let data = data else { XCTFail("Missing request data"); return } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👎 No sense in doing this. The test will fail if it's |
||
|
||
do { | ||
let JSON = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String: AnyObject] | ||
let JSON = try NSJSONSerialization.JSONObjectWithData(data, options: []) as? [String: AnyObject] | ||
XCTAssertEqual("test file\n", (JSON?["form"] as? [String: AnyObject])?["file"] as? String) | ||
} catch { | ||
XCTFail("Failed to read JSON.") | ||
} | ||
|
||
let HTTPResponse = response as! NSHTTPURLResponse | ||
guard let HTTPResponse = response as? NSHTTPURLResponse else { XCTFail("Bad HTTP response"); return } | ||
XCTAssertEqual(200, HTTPResponse.statusCode) | ||
|
||
expectation.fulfill() | ||
|
@@ -46,6 +54,7 @@ class SessionUploadTests: XCTestCase { | |
session.recordingEnabled = false | ||
let expectation = expectationWithDescription("Network") | ||
|
||
guard let testFile = testFile else { XCTFail("Missing testfile URL"); return } | ||
let data = encodeMultipartBody(NSData(contentsOfURL: testFile)!, parameters: [:]) | ||
|
||
session.uploadTaskWithRequest(request, fromData: data) { data, response, error in | ||
|
@@ -87,6 +96,12 @@ class SessionUploadTests: XCTestCase { | |
func writeDataToFile(data: NSData, fileName: String) -> NSURL { | ||
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] | ||
let documentsURL = NSURL(fileURLWithPath: documentsPath, isDirectory: true) | ||
|
||
do { | ||
try NSFileManager.defaultManager().createDirectoryAtURL(documentsURL, withIntermediateDirectories: true, attributes: nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually all the
Since there's no indication of where this crashed, and the test passed in Xcode itself, the actual error took like ½ a day to track down and was super annoying. So, while I like the theory that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @soffes Do you want me to put all the |
||
} catch { | ||
XCTFail("Failed to create documents directory \(documentsURL). Error \(error)") | ||
} | ||
|
||
let url = documentsURL.URLByAppendingPathComponent(fileName + ".tmp") | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -55,3 +55,32 @@ session.dataTaskWithRequest(NSURLRequest(URL: NSURL(string: "http://apple.com")! | |
``` | ||
|
||
If you don't call `beginRecording` and `endRecording`, DVR will call these for your around the first request you make to a session. You can call `endRecording` immediately after you've submitted all of your requests to the session. The optional completion block that `endRecording` accepts will be called when all requests have finished. This is a good spot to fulfill XCTest expectations you've setup or do whatever else now that networking has finished. | ||
|
||
### UI Testing | ||
|
||
To fake network requests in your app during UI testing, setup your app's network stack to check for the cassette file. | ||
|
||
``` swift | ||
var activeSession: NSURLSession { | ||
return fakeSession ?? defaultSession | ||
} | ||
|
||
private var fakeSession: NSURLSession? { | ||
guard let path = NSProcessInfo.processInfo().environment["cassette"] else { return nil } | ||
return Session(cassetteURL: NSURL(string: path)!) | ||
} | ||
``` | ||
|
||
And then send your app the cassette file's location when you launch it during UI testing. | ||
|
||
``` swift | ||
class LoginUITests: XCTestCase { | ||
func testUseValidAuth() { | ||
let app = XCUIApplication() | ||
app.launchEnvironment["cassette"] = NSBundle(forClass: LoginUITests.self).URLForResource("valid-auth", withExtension: "json")!.absoluteString | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✨ so cool! |
||
app.launch() | ||
} | ||
} | ||
``` | ||
|
||
If you use a URL that does not exist, DVR will record the result the same way it does in other testing scenarios. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be much better to get the length directly instead of guessing it's 5