I'm struggling a bit to figure out how to best test an app that uses Alamofire to help sync with server data.
I want to be able to test my code that uses Alamofire and processes JSON responses from a server. I'd like to mock those tests so that I can feed the expected response data to those tests without incurring real network traffic.
This blog post (http://nshipster.com/xctestcase/) describes how easy it is to Mock an object in Swift - but I'm not sure how to do that with Alamofire and its chained responses.
Would I mock the Manager? the Request? Response? Any help would be appreciated!
I'm adding another answer since I've just found this approach that in my opinion is easier and really simple to read and use.
I've created a dummy Alamofire class that contains only the functions and the types necessary for tests. Now I include this file in the test target instead of the real Alamofire.
For example I've created my version of the Request
class where I define a couple of static variables that I valorise depending on the test, and for this class I've implemented only the init
and the responseJSON
function.
public class Request {
var request:String?
struct response{
static var data:NSHTTPURLResponse?
static var json:AnyObject?
static var error:NSError?
}
init (request:String){
self.request = request
}
public func responseJSON(options: NSJSONReadingOptions = .AllowFragments, completionHandler: (NSURLRequest, NSHTTPURLResponse?, AnyObject?, NSError?) -> Void) -> Self {
completionHandler(NSURLRequest(URL: NSURL(string:self.request!)!), Request.response.data, Request.response.json, Request.response.error)
return self
}
}
Now I can mock a response in a test:
func testMytestFunction(){
var HTMLResponse = NSHTTPURLResponse(URL: NSURL(string: "myurl")!, statusCode: 200, HTTPVersion: "HTTP/1.1", headerFields: nil)
Request.response.data = HTMLResponse
Request.response.json = LoadDataFromJSONFile("MyJsonFile")
request(.POST, "myurl", parameters: nil, encoding: ParameterEncoding.JSON).responseJSON {
(request, response, JSON, error) -> Void in
// the JSON and response variable now contains exactly the data that you have passed to Request.response.data and Request.response.json
}
}
The request function is defined here:
public func request(method: Method, URLString: URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = .URL) -> Request {
return Request(request: URLString.URLString)
}
public func request(URLRequest: URLRequestConvertible) -> Request {
return Request(request: "fakecall")
}
This question is getting old, but I just encountered the same issue, and the solution is very easy when using OHHTTPStubs.
OHHTTPStubs just mocks the responses you get from NSURLSession, so it works well with Alamofire, and you get very good coverage of your code path.
For example, in your test case, just mock the response using:
OHHTTPStubs.stubRequestsPassingTest({
(request: NSURLRequest) -> Bool in
return request.URL!.host == "myhost.com"
}, withStubResponse: {
(request: NSURLRequest) -> OHHTTPStubsResponse in
let obj = ["status": "ok", "data": "something"]
return OHHTTPStubsResponse(JSONObject: obj, statusCode:200, headers:nil)
})
Waiting for an answer by @mattt I post an example of my code.
Let's say that we have a Client
class that is responsible for calling a simple web service. This class implements a function called userSignIn
that performs a sign in using the WS.
This is the code for the userSignIn
function:
func userSignIn(
#email:String,
password:String,
completionHandler: (Bool, String?, NSError?) -> Void
)-> Void
{
var parameters:[String:AnyObject] = [
"email":email,
"password":password,
]
Alamofire.request(.POST, Client.urlPath, parameters: parameters, encoding: ParameterEncoding.JSON).responseJSON {
(request, response, JSON, responseError) -> Void in
// Setup callback params
// HERE WE INJECT THE "FAKE" DATA--------
var operationComplete = false
var accessToken:String?
var error:NSError?
// --------------------------------------
if let statusCode = response?.statusCode {
// Check for errors and build response data
(operationComplete, accessToken, error) = self.checkSignInResponse(statusCode, JSON: JSON)
}
// Call the completion handler
completionHandler(operationComplete, accessToken, error)
}
}
The aim of the function is to get a token from the web service if the information passed by the user are correct.
The function checkSignInResponse
(I don't report its code since it's not useful for the answer) has the role to valorise the 3 variables operationComplete
, accessToken
and error
depending on the JSON response received.
Now that the 3 variables have a value we call the completionHandler
using them.
How to mock this function?!
To mock the response I override the userSignIn
function directly into the test function (as explained by the NSHipster article).
func testUserSignIn_whenParamsAreInvalid(){
class MockClient:Client {
override func userSignIn(#email: String, password: String, completionHandler:
(Bool, String?, NSError?) -> Void) {
// Set callback params
var operationComplete = false
var accessToken:String? = nil
var error:NSError? = NSError(domain: "Testing", code: 99, userInfo: nil)
completionHandler(operationComplete, accessToken, error)
}
}
signInViewController!.client = MockClient()
signInViewController!.loadView()
fillRegisterFieldsWithDataAndSubmit(femail(), password: fpassword())
XCTAssertNotNil(signInViewController!.error, "Expect error to be not nil")
}
then I substitute the client
inside the view controller that I'm testing using my "mocked" client. In this case I'm testing that the controller passes to the function information that are not valid so I check that the error
property of the controller is not nil. To force this data I simply set operationComplete
to false and I manual generate an NSError
.
Does it make any sense to you? I'm not sure that this test is a good test... but at least I can verify the data flow.
来源:https://stackoverflow.com/questions/26918593/unit-testing-http-traffic-in-alamofire-app