How to stub method with response block using OCMock - ios

I have a method that calls a request with a response block inside. I want to stub my response and return fake data. How can this be done?
-(void)method:(NSString*)arg1{
NSURLRequest *myRequest = ...........
[self request:myRequest withCompletion:^(NSDictionary* responseDictionary){
//Do something with responseDictionary <--- I want to fake my responseDictionary
}];
}
- (void)request:(NSURLRequest*)request withCompletion:(void(^)(NSDictionary* responseDictionary))completion{
//make a request and passing a dictionary to completion block
completion(dictionary);
}

If I understand you correctly, you want to mock request:withCompletion: and call the passed completion block.
Here is how I have done this in the past. I've adapted this code to your call, but I cannot check it for compilation errors, but it should show you how to do it.
id mockMyObj = OCClassMock(...);
OCMStub([mockMyObj request:[OCMArg any] completion:[OCMArg any]])).andDo(^(NSInvocation *invocation) {
/// Generate the results
NSDictionary *results = ....
// Get the block from the call.
void (^__unsafe_unretained completionBlock)(NSDictionary* responseDictionary);
[invocation getArgument:&callback atIndex:3];
// Call the block.
completionBlock(results);
});
You will need the __unsafe_unretained or things will go wrong. Can't remember what right now. You could also combine this with argument captures as well if you wanted to verify the passed arguments such as the setup of the request.

Related

Refactor code duplication of XCTestAssertions in a seperate method while writing unit test cases in Objective C

I have a unit test class with multiple test cases. Some test cases have common XCTest Assertions. These test cases implements an API call. This API call can have different input request parameters but the response is same. I have put test assertions on response. So, is it good by design to extract out this common assertions code on response in a separate method (not a test method) and call that method in the test methods?
Below is the code for reference:
- (void)testRequest {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
XCTAssertNil(error);
NSString *name = responseMessage[#"name"];
XCTAssert([name isEqualToString:#"Search"]);
NSString *response = responseMessage[#"response"];
XCTAssert([response isEqualToString:#"1"]);
hasReceivedResponse = YES;
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
Here appType can be A, B, C. Response as present in completion block remains the same. Can I extract completion block code to a separate method?
Refactored Code:
- (BOOL)receivedResponseForRequestMessage:(NSDictionary *)responseMessage error:(NSError *)error {
XCTAssertNil(error);
NSString *name = responseMessage[#"name"];
XCTAssert([name isEqualToString:#"Search"]);
NSString *response = responseMessage[#"response"];
XCTAssert([response isEqualToString:#"1"]);
}
-(void)testRequestForA {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
hasReceivedResponse = [self receivedResponseForRequestMessage:responseMessage error:error
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
-(void)testRequestForB {
AppType app = #"B";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
hasReceivedResponse = [self receivedResponseForRequestMessage:responseMessage error:error
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
}
Is this kind of refactoring correct by design?
Having a common XCTAssert does have at least one drawback. XCTAssert reports its line number, which enables you to find the failing test and quickly fix it. When your method testRequestForA fails, the failing line will be in receivedResponseForRequestMessage:error:.
One thing you can do in Objective-C is pass in a message after the expression being asserted. According to the docs, this is "An optional description of the failure. A literal NSString, optionally with string format specifiers." Your test results will contain this information, so if you identified the assertion well it will be easy to find the source line.
XCTAssert([response isEqualToString:#"1"], #"%# response failed", name); // Replace name with another string if appropriate
As a side note, Swift also allows you to also pass in the line number and/or file where the test failed, which makes it a lot easier to use assertions in helper methods.
In general, it's helpful to refactor test code to make it more expressive. This includes extracting assertion helpers.
In practice, this isn't easy to do in the Objective-C because assertions like XCTAssert are actually macros. They need to be macros at the call site in order to pick up __FILE__ and __LINE__. Unfortunately Apple chose to implement them entirely as macros, instead of as thin macros which call methods taking file name and line number arguments.
But in How to Structure Tests that Do Real Networking, I recommend not putting assertions within a block. Instead, have the block capture its arguments, and trigger the exit. This avoids issues with timeouts, and moves assertions to the end of the test where they are easier to read.
This would change the first part of your example to this:
- (void)testRequest {
AppType app = #"A";
NSDictionary *requestMessage = #{requestMessage};
__block BOOL hasReceivedResponse = NO;
__block NSDictionary *capturedResponseMessage = nil;
__block NSError *capturedError = nil;
[Class handleRequestMessage:requestMessage
appType:app
managedObjectContext:self.fixtures.managedObjectContext
completionBlock:^(NSDictionary *responseMessage, NSError *error) {
capturedResponseMessage = responseMessage;
capturedError = error;
hasReceivedResponse = YES;
}];
[[NSRunLoop currentRunLoop] runUntilCompletionIndicator:&hasReceivedResponse];
The hasReceivedResponse trigger can be replaced by an XCTestExpectation. This would let you use a timeout.
Now we can perform assertions against the captured arguments. But I'm going to change the assertions:
XCTAssertNil(error, #"error");
XCTAssertEqualObjects(responseMessage[#"name"], #"Search", #"name");
XCTAssertEqualObjects(responseMessage[#"response"], #"1", #"response");
}
First, I replaced the XCTAssert … isEqualToString: with XCTAssertEqualObjects. This is important, because when the assertion fails, it will report the 2 values that weren't equal instead of just "Failed". You can immediately see what the actual value was without using the debugger.
I also added a message to each assertion. This can be important when you have multiple assertions in a single test. It may not matter when sitting in front of your IDE, because you can click on a failure message to see which assertion triggered it. But when tests are running on a CI server, we want to log more information so that any failures can be diagnosed without hunting down line numbers.
If you still want to extract common assertions, I'd recommend either:
Write them entirely as macros so you get __FILE__ and __LINE__. Look at the definition of XCTAssert to get started.
Use OCHamcrest and write a custom matcher. Because OCHamcrest takes care of capturing file name and line number, the matcher is easier to write.
However, pulling the assertions out of the block made them so short they're hardly worth extracting. I'd focus instead on extracting a helper for the first part, capturing the block arguments.

Verify a method is called twice with two different parameters

I would like to test that a method is called twice, the first time passing YES for a parameter, the second time NO.
What complicates things is that the method I would like to test is a class method, I'm not sure whether this has anything to do with the issue I'm seeing.
My test looks like this:
- (void)testCreatesMessagesWithCorrectHTMLForcing {
id messageClassMock = OCMClassMock([MyMessage class]);
[messageClassMock setExpectationOrderMatters:YES];
[[[messageClassMock expect] andForwardToRealObject] messageForDictionary:[OCMArg any]
forceHTMLRendering:YES
inContext:[OCMArg any]];
[[[messageClassMock expect] andForwardToRealObject] messageForDictionary:[OCMArg any]
forceHTMLRendering:NO
inContext:[OCMArg any]];
NSMutableDictionary *mockJSON = [self.mockJSON mutableCopy];
MyThread *classUnderTest = [MyThread threadForDictionary:mockJSON
inContext:self.mockContext];
OCMVerifyAll(messageClassMock);
[messageClassMock stopMocking];
}
The threadForDictionary:inContext: method calls the messageForDictionary:forceHTMLRendering:inContext: for each message in the thread and needs an object as return value. That's why I added andForwardToRealObject, otherwise I get exceptions because of the return value being nil. As you can imagine from the signatures it's about parsing JSON to CoreData objects.
Adding this test makes all other tests in the same test file fail with the following message
unexpected method invoked: messageForDictionary:<OCMAnyConstraint: 0x7fab637063d0> forceHTMLRendering:NO inContext:<OCMAnyConstraint: 0x7fab63706eb0>
expected: messageForDictionary:<OCMAnyConstraint: 0x7fab6371cb20> forceHTMLRendering:YES inContext:<OCMAnyConstraint: 0x7fab6371fb50>"
I don't get why this happens as I call stopMocking in the end so the other tests should not be affected.
The following changes make the other tests run correctly:
Remove any of the two expectations. It doesn't matter which of two it is as long as there is only one.
Renaming the method to testZ. This way it's alphabetically after the other tests in the same file; thus, executed last and doesn't seem to affect them anymore.
As the setExpectationOrderMatters:YES does not seem to work I tried to check the order myself doing this:
- (void)testCreatesMessagesWithCorrectHTMLForcing {
id messageClassMock = OCMClassMock([MyMessage class]);
__block BOOL firstInvocation = YES;
[[[messageClassMock expect] andForwardToRealObject] messageForDictionary:[OCMArg any]
forceHTMLRendering:[OCMArg checkWithBlock:^BOOL (id obj) {
NSNumber *boolNumber = obj;
expect([boolNumber boolValue]).to.equal(firstInvocation);
firstInvocation = NO;
return YES;
}]
inContext:[OCMArg any]];
NSMutableDictionary *mockJSON = [self.mockJSON mutableCopy];
MyThread *classUnderTest = [MyThread threadForDictionary:mockJSON
inContext:self.mockContext];
expect(firstInvocation).to.equal(NO);
OCMVerifyAll(messageClassMock);
[messageClassMock stopMocking];
}
But the checkWithBlock: does not seem to be called. (The test fails at expect(firstInvocation).to.equal(NO);)
What's going on here?
Is there another (better?) way to write a test with OCMock that checks whether the method is called with the correct parameters in the correct order?
I finally got the first solution to work. The problem is that OCMock throws an exception if expectationOrderMatters is YES. Due to the exception the test is prematurely exited and the stopMocking is never called which leads to the mock not being cleaned up properly. Subsequent calls to the mocked method then fail with the same exception making all tests fail.
The solution is to ensure stopMocking is called even if everything goes wrong. I achieved this by using try-catch like this (the change to macros and using andReturn instead of andForwardToRealObject do not matter):
MyMessage *message = [MyMessage insertInManagedObjectContext:self.mockContext];
id messageClassMock = OCMStrictClassMock([MyMessage class]);
#try {
[messageClassMock setExpectationOrderMatters:YES];
OCMExpect([messageClassMock messageForDictionary:[OCMArg any]
forceHTMLRendering:NO
inContext:[OCMArg any]]).andReturn(message);
OCMExpect([messageClassMock messageForDictionary:[OCMArg any]
forceHTMLRendering:YES
inContext:[OCMArg any]]).andReturn(message);
MyThread *classUnderTest = [MyThread threadForDictionary:self.mockJSON
inContext:self.mockContext];
OCMVerifyAll(messageClassMock);
}
#catch (NSException *exception) {
XCTFail(#"An exception occured: %#", exception); // you need this, otherwise the test will incorrectly be green.
}
#finally {
[messageClassMock stopMocking];
}
Notice the XCTFail in the catch-block: You need to include this, otherwise your test will be green although an exception occurred.

Executing functions and blocks through time

I don't understand how Objective-C loop system works. I have function (hope names are right, rather check in code) which executes query from Health Kit. I got my mind blown when I realised that function pass return value before query finishes.
__block bool allBeckuped = true;
HKSampleQuery *mySampleQuery = [[HKSampleQuery alloc] initWithSampleType:mySampleType
predicate:myPredicate
limit:HKObjectQueryNoLimit
sortDescriptors:#[mySortDescriptor]
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if(!error && results)
{
for(HKQuantitySample *samples in results)///main hk loop
{
allBeckuped = false;
NSLog(#"1");
}
}
}];//end of query
[healthStore executeQuery:mySampleQuery];
NSLog(#"2");
return allBeckuped;
I'm trying to check if there are any new data, but I don't know where to put condition for that, because nslog2 is called before nslog 1.
Any words I should Google up?
Any words I should google up?
You can start with: asynchronous design, blocks, GCD/Grand Central Dispatch should help as well - you're not using it but asynchronous designs often do.
Look at the initWithSampleType: method you are calling, it is an example of a method following the asynchronous model. Rather than return a result immediately, which is the synchronous model you are probably used to, its last argument, resultsHandler:, is a block which the method calls at some future time passing the result of its operation to it.
This is the pattern you will need to learn and follow.
Your method which contains the call to initWithSampleType: cannot return a result (e.g. your allBeckuped) synchronously. So it needs to take a "results handler" block argument, and the block you pass to initWithSampleType: should call the block passed to your method - and so the asynchronous flow of control is weaved.
HTH

nil checking on blocks [duplicate]

This question already has answers here:
Is it okay to pass in NULL to a block parameter?
(2 answers)
Closed 9 years ago.
I have a method that does an asynchronous network call, and then passes the success or failure results back via blocks:
- (void) loginWithSuccess:(void (^)(id responseObject))success failure:(void (^)(NSError* error))failure {
...
if(error) {
failure(error);
}
else {
success(responseObject);
}
}
I noticed that if I call this method and pass in nil as my blocks, my application will crash with EXEC_BAD_ACCESS:
[manager loginWithWithSuccess:nil failure:nil];
But, if I pass in empty blocks, it runs fine:
[manager loginWithWithSuccess:^(id responseObject){} failure:^(NSError *error){}];
I assume this is because at runtime you can't pass parameters to nil? So, when defining my methods that take blocks, should I always check that the blocks are not nil before calling them?
Just looking at Apple's Frameworks, some methods with block parameters accept NULL/nil as the block argument (e.g. animateWithDuration:animations:completion:), others don't (e.g. enumerateObjectsUsingBlock:).
If you're designing an API, you have to make that decision. If it makes sense to not have a block, you should accept nil and check before executing the block, otherwise you should throw an assertion like [#[] enumerateObjectsUsingBlock:nil] does:
'NSInvalidArgumentException', reason: '*** -[NSArray enumerateObjectsUsingBlock:]: block cannot be nil'
So why are you getting EXEC_BAD_ACCESS?
When invoking a block you are dereferencing the address, which you obviously can't do if it's not pointing to an actual block. There is a great explanation in this answer.
Please try the following example to understand call block logic:
void (^printString)(NSString*) = ^(NSString* arg) {
NSLog(#"%#", arg);
};
//(1) printString = ^(NSString* arg){};
//(2) printString = NULL;
printString(#"1");
In the console you will see "1". Then uncomment (1) and console reveals "Called" but no errors!
And at last, uncomment (2) and get EXEC_BAD_ACCESS. It is your situation exactly.
Called block must be not NULL or nil. You need to check existence of passing blocks in loginWithSuccess before call.

blocks in nsdictionary?

So I'm storing block actions into a nsmutabledictionary and then recalling them when a response comes back on a websocket. This turns async request into a block syntax. Here's the stripped down code:
- (void)sendMessage:(NSString*)message responseAction:(void (^)(id))responseAction
{
NSString *correlationID = (NSString*)[[message JSONValue] objectForKey:#"correlationId"];
[self.messageBlocks setObject:responseAction forKey:correlationID];
NSLog(#"Sending message: %#", correlationID);
[webSocket send:message];
}
- (void)webSocket:(SRWebSocket *)wsocket didReceiveMessage:(id)message;
{
NSString *correlationID = (NSString*)[[message JSONValue] objectForKey:#"correlationId"];
NSLog(#"Incoming message. CorrelationID: %#", correlationID);
void (^action)(id) = nil;
if (correlationID) {
action = [messageBlocks objectForKey:correlationID];
if (action) action([message JSONValue]);
[messageBlocks removeObjectForKey:correlationID];
}
}
Note: The server responds with a correlationID that is sent with the request. So each response is linked to each request through that id.
This works perfectly, better than I expected. The question I have is that is it safe to run blocks this way? Is calling [messageBlocks removeObjectForKey:correlationID]; enough to remove it from the memory. I remember pre-ARC, block_release was an option.
You need to copy stack-based blocks in order to safely store them in a container.
[self.messageBlocks setObject:[responseAction copy] forKey:correlationID];
For non-ARC code, you need to -autorelease it also.
[self.messageBlocks setObject:[[responseAction copy] autorelease] forKey:correlationID];
Hope that helps.

Resources