Async method testing using XCTest - ios

I am new to XCTest and started writing test cases. I have written couple of functional test cases, but I got stuck in below async method call. I read XCTestExpectation class reference but not able to mock properly.
It will be really helpful If get to know how to test below method.
Here are the details of method that I want to test,
- (void)getStudentInfo:(void (^)(BOOL))success failure:(void (^)(NSError *error))failure {
// Request formation CODE
// Here I have post call to AFNetworking
[httpSessionManager POST:uri
parameters:para
success:^(NSURLSessionDataTask *task, id responseObject) {
failure:^(NSURLSessionDataTask *task, NSError *error) {
}]
}

You need to look into XCTest expectations which provide support for async testing. Here is a simplified example (in Swift, but the same applies for Objective-C)
func testAsync() {
let expectation = expectationWithDescription("Waiting for something")
let someObject = ...
someObject.doSomethingWithCompletion({ () in
expectation.fulfill()
})
waitForExpectationsWithTimeout(3.0, handler: nil)
// Verify Results
}
The simple explanation is that the test will wait at the waitFor... until the fulfil() method is called. Or timeout and throw an error.
You can also set expectations on notifications and properties. Check the XCTest class doco for details.

Related

"Parameter name omitted" Error when migrate objc to swift , dealing with closure

I'm migrating my object-c project to swift, I can use closure for block , but there is error "Parameter name omitted", I have to add the paramater by my self, instead of generated automatically by xcode
here is my swift closure code, the closure has parameters
func GET(urlSting:String!,
parameters:[String: AnyObject]?,
success:((responseObject:AnyObject?) -> Void)?,
failure:((error:NSError?) -> Void)?){
// somethings
}
here is my code generated in project-Swift.h, block has no parameter
- (void)GET:(NSString * __null_unspecified)urlSting
parameters:(NSDictionary<NSString *, id> * __nullable)parameters
success:(void (^ __nullable)(id __nullable))success
failure:(void (^ __nullable)(NSError * __nullable))failure;
here is my code in objc.m, block has no parameter,
[[APIClientS sharedClient] GET:"www.google.com/xxxx"
parameters:nil
success:^(id _Nullable) {
} failure:^(NSError * _Nullable) {
}];
I need to add the parameters by myself,
failure:^(NSError * _Nullable error)
It is really a boring work , how can I generate the parameter name automatically ??
Edit -2015-11-22 09:48:24
Answer Bobj-C's Question:
what do you mean "block has no parameter" ?
As the Following the Pic show, the block not have parameter error, I have to add it by myself

Calling Objective-c function in swift

Im trying to implement CometChat in my swift application. I managed to import the Objective-c framework successfully via a bridging header. But now I'm stuck trying to call Objective-C methods from swift.
This is the method from the interface i want to call:
- (void)loginWithURL:(NSString *)siteURL
username:(NSString *)username
password:(NSString *)password
success:(void(^)(NSDictionary *response))success
failure:(void(^)(NSError *error))failure;
And this is how the method is called from Objective-C:
[cometChat loginWithURL:#"localhost/cometchat/" username:usernameTextField.text password:passwordTextField.text success:^(NSDictionary *response) {
NSLog(#"SDK log : Username/Password Login Success %#",response);
[self handleLogin];
} failure:^(NSError *error) {
NSLog(#"SDK log : Username/Password Login Error%#",error);
[self handleLoginError:#[#0,error]];
}];
So far i have this:
cometChat.loginWithURL("localhost/cometchat/", username: EmailField.text, password: PasswordField.text){
(success: [NSDictionary], failure:NSError) in {
println("did i make it here?")
}
}
The problem is, it says that there is missing argument "success", but its a mystery to me how it can be an argument, when it clearly returns the response. I want to know how to put together this method call. I also used the objectivec2swift converter, but it wasn't any help. Also, i have no clue what the # means before the #[#0,error]
I know its a beginners question, but i already wasted a whole day on this, since i couldn't find any tutorials on how to call such "complex" Obj-C methods from swift.
Try this :-
cometChat.loginWithURL("localhost/cometchat/", username: "abc", password: "123", success: { (response) -> Void in
print("SDK log : Username/Password Login Success \(response)")
}) { ( error) -> Void in
print("SDK log : Username/Password Login Error \(error)")
}
When you look at the Objective-C signature, you see that the method takes two closures: success is a void function that takes a dictionary, and failure is a void function that takes an error.
In your Swift code you have only one closure: a void function that takes a dictionary and an error.
You either need to change the Objective-C method to take just one closure, or change the Swift code to provide two closures.
When you call a function, and the last parameter is a block / closure, then you can write the last parameter after the function call in { }. That applies to the last block only.
Anyway, you are trying to pass a closure with two parameters success and failure. You need to pass two closures, one as the success parameter of your function, with a parameter response, and one either as the failure parameter of your function, or following the function, with a parameter error.

How one tests http requests in iOS 8?

In ruby I used to test http requests with vcr gem which recorded the request so the tests didn't send request to real host. Is there anything like this in iOS8 world?
The requests I want to test really need to be recorded since those requests may be outdated in some time and will return some other response
P.S. It would be great if it was some default Apple/iOS approach/library like XCTest for testing in general
What you want is something like OHHTTPStubs or Nocilla or AMY server. All of them essentially use NSURLProtocol to intercept your request and allow you to designate a response. We used OHHTTPStubs but pick the one with the feature set closest to your use case.
Here's an example of an OHHTTPStubs implementation in a unit test for a service that talks to a single REST endpoint:
NSString *loadRoomJSON = #{ #"key" : #"value" }; /* some JSON */
NSNumber identifier = #1;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSString *url = [NSString stringWithFormat:#"v1/user/%#/room", identifier];
XCTAssert([request.URL.relativePath containsString:url], #"Expected certain URL");
return YES;
} andRespond:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithJSONObject:loadRoomJSON statusCode:200 headers:nil];
}];
XCTestExpectation *loadPromise = [self expectation:#"Room loaded"];
[service loadRoomOnSucceed:^(Room *room) {
// Do your asserts here. For us, the JSON is mapped to an object
// so for example you could assert that the object is mapped correctly
[loadPromise fulfill];
} onFail:^(NSError *error) {
expect(error).to.beNil();
}];
[self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
expect(error).to.beNil();
}];
In reality our tests are shorter since we write wrapper/helpers to make it read better so this is an exploded-out version. Should give you the general idea. OHHTTPStubs (if you use it) has helper functions to load responses directly from files as well.
Im not sure if I understood you correct. But if I understand you right, you should be able to use XCTest to test your request and response.
class Tests:XCTestCase{
func testing(){
var expectation = self.expectationWithDescription("Your request")
var url = NSURL(string: "http://yourUrl.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if let httpRes = response as? NSHTTPURLResponse {
println("status code=",httpRes.statusCode)
//200 means OK
if httpRes.statusCode == 200 {
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
}else{
println("error \(error)")
}
}
}
}

How to run unit tests on code that has failing assertions in Xcode?

In Xcode i am running tests on creating users based on ID. When a wrong ID is set the test should fail. Though this test fails since the method it tests has assertions in itself:
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
STFail(#"should not be able to create player with no ID");
} failure:^(NSError *error) {
}];
the method called:
- (void)findAndCreateUserWithID:(NSNumber *)ID success:(void (^)(Player *))createdPlayer failure:(void (^)(NSError *error))failure
{
NSParameterAssert(ID);
Will fail the test when parameter ID is nil. I know this is a pretty stupid example since it will always fail, but there are more assertions allready in code that are more useful. Whats the best practice on running Xcode unit tests which test code that allready has assertions?
As of late 2014, if you're using the new testing framework XCTest, you'll want to use XCTAssertThrowsSpecificNamed instead of the older STAssertThrowsSpecificNamed method:
void (^expressionBlock)() = ^{
// do whatever you want here where you'd expect an NSParameterAssertion to be thrown.
};
XCTAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException);
NSParameterAssert throws an NSInternalInconsistencyException (source) when its assertion fails. You can test that this happens with the STAssertThrowsSpecificNamed macro. For example:
void (^expressionBlock)() = ^{
[[Apiclient sharedClient] findAndCreateUserWithID:nil success:^(Player *player) {
} failure:^(NSError *error) {
}];
};
STAssertThrowsSpecificNamed(expressionBlock(), NSException, NSInternalInconsistencyException, nil);
I use an expression block there to make it easier to put that much code in the macro.

How to unit test api calls using AFNetworking

I have an iOS app I'm working on, which connects to a third-party web service. I have around 50 different calls, and want to write unit tests using Kiwi, but I have no idea where to start.
As I'm not responsible for the API, I need to just check that my calls are pointing to the correct URL, using the correct GET or POST method.
Is there any way to test this properly?
Heres an example of one of my calls:
+ (void)listsForUser:(NSString *)username
response:(void (^)(NSArray *lists))completion
{
NSString *path = [NSString stringWithFormat:#"user/list.json/%#/%#", TRAKT_API_KEY, username];
[TNTraktAPIManager requestWithMethod:GET
path:path
parameters:nil
callback:^(id response) {
completion(response);
}];
}
Which calls the following helper method
+ (void)requestWithMethod:(HTTPMethod)method
path:(NSString *)path
parameters:(NSDictionary *)params
callback:(void (^)(id response))completion
{
NSString *methodString = #"POST";
if (method == GET) {
methodString = #"GET";
}
// Setup request
NSURLRequest *request = [[TraktAPIClient sharedClient] requestWithMethod:methodString
path:path
parameters:params];
// Setup operation
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
success:^(NSURLRequest *request,
NSHTTPURLResponse *response,
id JSON) {
id jsonResults = JSON;
completion(jsonResults);
} failure:^(NSURLRequest *request,
NSHTTPURLResponse *response,
NSError *error,
id JSON) {
id jsonResults = JSON;
completion(jsonResults);
NSLog(#"%s: error: %#", __PRETTY_FUNCTION__, error);
}];
// TODO: Queue operations
[operation start];
}
If you set a shouldEventually expectation on the helper class and use the receive:(SEL)withArguments:(id)... form, then you can check that the argument received is what you'd expect it to be.
Two gotchas worth knowing about are setting the expectation before making the call; and using the shouldEventually form rather than should so that the test is delayed long enough for the call to be made.
I highly recommend checking out OHHTTPStubs for unit testing your API Classes.
Unit tests should be deterministic and adding internet a potentially unpredictable API into the mix makes the testing conditions non-deterministic.
OHTTPStubs will allow you to stub the response to your outgoing HTTP Requests. Basically, it intercepts your HTTP Traffic and if the request matches criteria that you set, it gives a canned response that you declare in JSON (rather than an unpredictable result from the API). This allows you to configure different response scenarios in your test classes: ie. 404 error, partial response data, etc.. to use in your tests.
Here's an example:
I created this JSON Stub and saved as a JSON file:
{
"devices" : [
{
"alarm" : {
"alarm_id" : 1,
"attack_time" : "<null>",
"defined_time" : "2014-04-14T04:21:36.000Z",
"device_id" : 7,
"is_protected" : 0
},
"device_type" : "iPhone",
"id" : 7,
"os_version" : "7.1"
}
],
"email" : "mystubemail#gmail.com",
"facebook_id" : 5551212,
"id" : 3,
"name" : "Daffy Duck"
}
Whenever I network requests are made in the API Call, this JSON is returned because of this OHHTTPStub which is declared in the test class to run before all tests.
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
BOOL stringFound;
if ([[request.URL description] rangeOfString:[NSString stringWithFormat:#"%#devices/register?", BASEURL]].location != NSNotFound)
{
stringFound = YES;
}
NSLog(#"%d", stringFound);
return stringFound;
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
// Stub it with our "RegisterDevice.json" stub file
return [[OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(#"RegisterDevice.json",bundle)
statusCode:200 headers:#{#"Content-Type":#"text/json"}] requestTime:1.0 responseTime:1.0];
}];
I'm not sure if Kiwi allows for async testing, but if not I also recommend looking into Specta and the matching framework Expecta. They allow for super easy Asynchronous unit testing, which when combined with OHHTTPStubs provides all you need to unit test API Calls.

Resources