This is how I get the instance of my network client:
let networkClient = DBNetworkClient(baseURL: NSURL(string: "http://mysite.pl/api"))
I also have one method:
func citiesWithParameters(parameters: [String: String], completionBlock: DBSearchOptionHandler) {
GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
This is how I call this method:
networkClient.citiesWithParameters(parameters, completionBlock: { cities, error in
//do sth
})
This way I pass some parameters, and get the real response from server. I would like to mock THAT response when I ask for this. How to do this?
func testCities() {
let mockNetworkClient = OCMockObject.mockForClass(DBNetworkClient.classForCoder())
//what should I perform here to be able to do sth like this:
let expectation = expectationWithDescription("")
mockNetworkClient.citiesWithParameters(["a": "b"]) { cities, error in
expectation.fulfill()
XCTAssertNotNil(cities)
XCTAssertNil(error) //since I know what is the response, because I mocked this
}
waitForExpectationsWithTimeout(10, handler: nil)
}
And this is how my method GET
is defined within DBNetworkClient
:
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
return super.GET(URLString, parameters: parameters, success: { (operation, response) in
print("GET \(operation.request.URL)")
print("GET \(response)")
success(operation, response)
}, failure: { (operation, error) in
print("GET \(operation.request.URL)")
print("GET \(operation.responseObject)")
failure(operation, error)
})
}
This article: Writing mock tests for AFNetworking is unfortunately not helpful. It is not working for me.
I do not have experience with Alamofire therefore I don't know where is declared your GET
method but you should definitely stub this method instead of citiesWithParameters
.
For example if it's declared in your DBNetworkClient
:
func testCities()
{
//1
class DBNetworkClientMocked: DBNetworkClient
{
//2
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
//3
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//4
let sut = DBNetworkClientMocked()
sut.citiesWithParameters(["a":"b"]) { cities, error in
//5
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
So what happens here:
DBNetworkClient
, therefore making a 'Mock' for it as described in article you posted. In that class we will override only methods that we want to change(stub) and leave others unchanged. This was previously done with OCMock and was called Partial Mock.DBNetworkClient
has specific constructor you must call this constructor instead of default one - ().So far so good. However if GET
method is part of Alamofire you need to use a technique called Dependency Injection(you could google it for more info).
So if GET
is declared inside another class and is only referenced in citiesWithParameters, how we can stub it? Let's look at this:
//1
class DBNetworkClient
{
//2
func citiesWithParameters(parameters: [String: String], networkWorker: Alamofire = Alamofire(), completionBlock: DBSearchOptionHandler) {
//3
networkWorker.GET("cities", parameters: parameters, success: { operation, response in
if let error = NSError(response: response) {
completionBlock([], error)
} else {
let cities = DBCity.parseCitiesWithDictionary(response as! NSDictionary)
completionBlock(cities, nil)
}
}) { operation, error in
completionBlock([], error)
}
}
}
func testCities()
{
//4
class AlamofireMock: Alamofire
{
override func GET(URLString: String!, parameters: AnyObject!, success: ((AFHTTPRequestOperation!, AnyObject!) -> Void)!, failure: ((AFHTTPRequestOperation!, NSError!) -> Void)!) -> AFHTTPRequestOperation! {
success(nil, ["San Francisco", "London", "Sofia"])
}
}
//5
let sut = DBNetworkClient()
sut.citiesWithParameters(["a":"b"], networkWorker: AlamofireMock()) { cities, error in
//6
XCTAssertNotNil(cities)
XCTAssertNil(error)
}
}
citiesWithParameters
to receive one more parameter. This parameter is our dependency injection. It is the object that have GET
method. In real life example it will be better this to be only protocol as citiesWithParameters
doesn't have to know anything more than this object is capable of making requests. citiesWithParameters
to fulfill new parameters requirement.GET
methodcitiesWithParameters
we pass our mocked object as networkWorker. This way our stubbed GET
method will be called and we will get our expected data from 'fake' server.Please note that those two examples do not use OCMock, instead they rely entirely on Swift power! OCMock is a wonderful tool that we used in great dynamic language - Objective-C. However on Swift dynamism and reflection are almost entirely missing. Thats why even on official OCMock page we had following statement:
Will there be a mock framework for Swift written in Swift? Maybe. As of now it doesn't look too likely, though, because mock frameworks depend heavily on access to the language runtime, and Swift does not seem to provide any.
The thing that is missing with in both implementations provided is verifying GET
method is called. I'll leave it like this because that was not original question about, and you can easily implement it with a boolean value declared in mocked class.
One more important thing is that I assume GET
method in Alamofire is instance method, not a class method. If that's not true you can declare new method inside DBNetworkClient
which simply calls Alamofire.GET
and use that method inside citiesWithParameters
. Then you can stub this method as in first example and everything will be fine.