I'm new to OCMock and trying to unit test a camera application that uses AVCaptureSession.
I'm trying to unit test the method that captures a still image and passes it along.
I'm having trouble mocking AVCaptureStillImageOutput's captureStillImageAsynchronouslyFromConnection:completionHandler:. The issue is in passing a CMSampleBufferRef to the completionHandler. I don't really care what the contents of the CMSampleBufferRef is other than it can't be nil/null. With the unit test case, the only time it is referenced in the completion handler is if ( imageDataSampleBuffer ) {...every other use I have mocked.
Here is what I've tried:
Setup:
NSError *error = nil;
CMSampleBufferRef imageDataSampleBuffer;
stillImageOutputMock = OCMStrictClassMock([AVCaptureStillImageOutput class]);
Try #1:
[[stillImageOutputMock expect] captureStillImageAsynchronouslyFromConnection:connectionMock
completionHandler:([OCMArg invokeBlockWithArgs:imageDataSampleBuffer, &error, nil])];
Gives compiler error:
/Users/.../UnitTests/Unit/CameraViewController/CameraViewControllerTests.m:195:152: Implicit conversion of C pointer type 'CMSampleBufferRef' (aka 'struct opaqueCMSampleBuffer *') to Objective-C pointer type 'id' requires a bridged cast
Xcode offers to "fix" it with this: (which I tried; try #2)
[[stillImageOutputMock expect] captureStillImageAsynchronouslyFromConnection:connectionMock
completionHandler:([OCMArg invokeBlockWithArgs:(__bridge id)(imageDataSampleBuffer), &error, nil])];
But this generates a EXC_BAD_ACCESS even though I've mocked all methods that actually use the imageDataSampleBuffer inside completionHandler. The exception is coming from in OCMock where it is adding the imageDataSampleBuffer to an array of args
+ (id)invokeBlockWithArgs:(id)first,... NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *params = [NSMutableArray array];
va_list args;
if(first)
{
[params addObject:first]; <<<<<< EXCEPTION HERE. first isn't an object
va_start(args, first);
Try #3:
OCMock documentation states non-object arguments must be wrapped in value objects and the expression must be wrapped in round brackets., so I tried:
[[stillImageOutputMock expect] captureStillImageAsynchronouslyFromConnection:connectionMock
completionHandler:([OCMArg invokeBlockWithArgs:#(imageDataSampleBuffer), &error, nil])];
but the compiler complains:
/Users/.../UnitTests/Unit/CameraViewController/CameraViewControllerTests.m:196:119: Illegal type 'CMSampleBufferRef' (aka 'struct opaqueCMSampleBuffer *') used in a boxed expression
Suggestions?
I was able to get it running with [OCMArg invokeBlock] like:
[[stillImageOutputMock expect] captureStillImageAsynchronouslyFromConnection:connectionMock
completionHandler:[OCMArg invokeBlock]];
but then the completion handler gets 0x0 for imageDataSampleBuffer and all of the interesting functionality in the completion handler is skipped.
Ah, I found the solution digging into the OCMock tests (specifically OCMockObjectTests.m)
OCMOCK_VALUE() did the trick. Here is the working syntax:
NSError *error = nil;
CMSampleBufferRef imageDataSampleBuffer;
stillImageOutputMock = OCMStrictClassMock([AVCaptureStillImageOutput class]);
[[stillImageOutputMock expect] captureStillImageAsynchronouslyFromConnection:connectionMock
completionHandler:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(imageDataSampleBuffer), OCMOCK_VALUE(&error), nil])];
Related
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.
I am now confused by pointer to pointer even though I've read Why does NSError need double indirection? (pointer to a pointer) and NSError * vs NSError ** and much more.
I've done some thinking and still got some questions.
Here I wrote this:
NSError *error = [NSError errorWithDomain:#"before" code:0 userInfo:nil];
NSLog(#"outside error address: %p", &error];
[self doSomethingWithObj:nil error:&error];
In order to test the above NSError method, I wrote this:
- (id)doSomethingWithObj:(NSObject *)obj error:(NSError *__autoreleasing *)error
{
NSLog(#"inside error address: %p", error);
id object = obj;
if (object != nil)
{
return object;
}
else
{
NSError *tmp = [NSError errorWithDomain:#"after" code:0 userInfo:nil];
*error = tmp;
return nil;
}
}
But I found that the two logging addresses are different. Why is that?
2016-08-19 19:00:16.582 Test[4548:339654] outside error address: 0x7fff5b3e6a58
2016-08-19 19:00:16.583 Test[4548:339654] inside error address: 0x7fff5b3e6a50
Shouldn't they be the same since that was just a simple value copy? If they should be different, how can pointer to pointer end up pointing to the same NSError instance?
The variable in the caller has type NSError*. The address has type NSError* *. The function expect NSError* __autoreleasing *. Therefore the compiler creates a hidden variable of type NSError* __autoreleasing, copies the NSError* into the hidden variable before the call, and copies it back after the call to get the semantics of __autoreleasing right.
So, after initialisation on the first line, error is a pointer to an NSError object.
In the first log, you are logging the address at which that pointer is held. That's the effect of the & address-of operator. Anyway, that is the address gets passed into the doSomething method.
You're passing in: pointer -> pointer -> nserror-object.
But notice the double indirection in the signature of doSomething. The autoreleasing annotation makes it hard to spot, but its NSError **.
So the compiler takes your parameter and 'unwraps' it twice.
It starts as pointer -> pointer -> nserror-object. Then after the first indirection it becomes pointer -> nserror-object. Then after the second indirection it becomes nserror-object.
Ergo, you are logging two different things. The first is the address of the pointer to the nserror-object. The second is the address of the nserror-object itself.
EDIT:
#MANIAK_dobrii points out that the object pointed to by error is itself different in the before and after case.
That's true. If an error occurs in doSomething then it creates an entirely new NSError instance in the else clause. It then loads that back into the error pointer. That's why you would see two different addresses, afterwards the error pointer is pointing to another object entirely.
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.
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.
I have a block;
typedef void (^SIResponseHandler) (id obj, NSString *error);
and a method:
+ (void)uploadPhoto:(UIImage *)photo
toPathForComponents:(NSArray *)components
completionHandler:(SIResponseHandler)responseHandler;
and another method which calls the above method:
+ (void)updateProfilePhoto:(UIImage *)photo handler:(SIResponseHandler *)handler {
NSArray *components = #[#"users", sharedInstance.username, #"profile", #"photo", #"upload"];
[SIRequest uploadPhoto:photo
toPathForComponents:components
progressHandler:nil
completionHandler:handler];
}
In the last line, I get this error:
Sending '__autoreleasing SIResponseHandler *' (aka 'void (^__autoreleasing *)(__strong id, NSString *__strong)') to parameter of incompatible type 'SIResponseHandler' (aka 'void (^)(__strong id, NSString *__strong)')
I have no idea what this means. Can someone please explain what's going on ?
Thanks
It appears that SIResponseHandler is a block type, and as such should not be suffixed as a pointer with * in an argument list, unless you know exactly what you are doing.