OCMock - How to expect the content of an nsarray - objective-c

I want to test that the values i insert in a database are sent back to the delegate of my class.
I tried to mock the delegate and expect the array i used to populate the database. It fails because the 2 NSArray have the same content but are different objects.
I've tried implementing isequal and hash methods on my model whithout success.
Here is the error log :
'OCMockObject[NewsListViewController]: unexpected method invoked: service:<RSSService-0x4c25b90-324400011.636966: 0x4c25b90> didFinishParsingRSSWithItems:(
description102362,
description102362,
description102362,
description102362,
description102362
)
expected: service:<OCMAnyConstraint: 0x4c10f30> didFinishParsingRSSWithItems:(
description102362,
description102362,
description102362,
description102362,
description102362
)'
How can i do that ?
Here is my test :
- (void) testServiceShouldNotLoadArticlesFromRSSFeedIfArticlesInDatabase {
NSArray *fakeArticles = [TestUtils createArticles:5];
[[DatabaseManager sharedManager] saveArticles:fakeArticles];
RSSService *mockService = [OCMockObject partialMockForObject:service];
id mockDelegate = [OCMockObject mockForClass:NewsListViewController.class];
[[mockDelegate expect] service:[OCMArg any] didFinishParsingRSSWithItems:fakeArticles];
mockService.delegate = mockDelegate;
[mockService loadAllArticles];
[mockService verify];
[mockDelegate verify];
}
and here is the method i'm testing :
- (void) loadAllArticles {
NSArray *articles = [self articlesFromDatabase];
[self.delegate service:self didFinishParsingRSSWithItems:articles];
}
Thanks for your help,
Vincent

Get OCHamcrest! It provides a bunch of matchers that work with OCMock and make for much cleaner tests. Here's what your expectation would look like:
[[mockDelegate expect] service:[OCMArg any] didFinishParsingRSSWithItems:contains(article1, article2, article3, article4, article5, nil)];
// or, if order is unpredictable
[[mockDelegate expect] service:[OCMArg any] didFinishParsingRSSWithItems:containsInAnyOrder(article1, article2, article3, article4, article5, nil)];
If you really don't want to use hamcrest for some reason, you can create a method in your test class that evaluates the articles array. But I think it's kind of ugly:
- (void) testServiceShouldNotLoadArticlesFromRSSFeedIfArticlesInDatabase {
NSArray *fakeArticles = [TestUtils createArticles:5];
[[DatabaseManager sharedManager] saveArticles:fakeArticles];
RSSService *mockService = [OCMockObject partialMockForObject:service];
id mockDelegate = [OCMockObject mockForClass:NewsListViewController.class];
[[[mockDelegate stub] andCall:#selector(service:verifyFiveArticles:) onObject:self] service:[OCMArg any] didFinishParsingRSSWithItems:[OCMArg any]];
mockService.delegate = mockDelegate;
[mockService loadAllArticles];
[mockService verify];
[mockDelegate verify];
}
-(void)service:(id)service verifyFiveArticles:(NSArray *)articles {
STAssertEquals(5, [articles count]);
}

Is it possible that the two arrays have the same content but different addresses? Maybe trying comparing with isEqualToArray
[[mockDelegate expect] service:[OCMArg any]
didFinishParsingRSSWithItems:[OCMArg checkWithSelector:#selector(isEqualToArray:)
onObject:expectedArray]]

Related

RACObserve subscribeNext didn't execute when value of the observed item changed?

hello everybody I have an weird issue , I putted this code in my bind method :
[RACObserve(self.viewModel,contacts) subscribeNext:^(id x) {
[self.contactsTableView reloadData];
}];
but when the contacts in my viewModel changed , the subscribeNext didn't execute !! , I checked if the value changed by debugging and it changed normally !!
this is where the value is changed in my view Model (Simplified) :
I initilized it here :
- (instancetype)init {
self.contacts = [[NSMutableArray <Contact *> alloc]init];
}
and changed here
#pragma mark - load and filter methods
- (RACCommand *)loadContactsCommand {
ContactsNetworkManager *contactNetworkManager = [ContactsNetworkManager sharedManager];
return [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [[contactNetworkManager getAllContactsSignal] map:^id(NSMutableArray<Contact *> * value) {
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"firstName" ascending:YES];
[value sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
[_contacts removeAllObjects];
[_contacts arrayByAddingObjectsFromArray:value];
return value;
}];
}];
}
#pragma mark - Actions
- (void)loadContacts {
[self.loadContactsCommand execute:nil];
}
#end
find it , actually the solution is too simple , I had to use self rather than on an independent reference to the NSMutableArray object like _ . In other words, it won't work if you do that , because the KVO established by RACObserve() is relative to the object you pass in as its first parameter (self, in this case), so only KVC-compliant mutations that pass through the observed object will trigger the observation notifications.

Cannot stub a method ([NSData initWithBase64EncodedString])

I'm newcomer to Objective-C and OCMock. I created this trivial test and expect it to pass, but it fails:
-(void)testMock
{
id dataMock = [OCMockObject niceMockForClass:[NSData class]];
[[[dataMock stub] andReturn:dataMock] initWithBase64EncodedString:[OCMArg any]
options:[OCMArg any]];
NSData *ret = [dataMock initWithBase64EncodedString:#"dGVzdA=="
options:NSDataBase64DecodingIgnoreUnknownCharacters];
XCTAssertEqualObjects(dataMock, ret);
}
As you see, I stub a method and command it to return dataMock, but it returns null:
Assertions: ((dataMock) equal to (ret)) failed:
("OCMockObject(NSData)") is not equal to ("(null)")
Note: Testing with init instead of initWithBase64EncodedString, works as expected and passes.
I'm using XCode 6.0.1 and programming for iOS 8.0.
I found out that this the famous problem of matching primitives in OCMock. options in the mentioned method, takes a primitive and [OCMArg any] does not match it.
So for now I'm going with the exact used value:
[[[dataMock stub] andReturn:dataMock] initWithBase64EncodedString:[OCMArg any]
options:NSDataBase64DecodingIgnoreUnknownCharacters];

How to stub method block in Kiwi?

I want to stub a method which takes a block as a parameter using Kiwi. Here is the full explanation with code:
I have a class named TestedClass which has a method testedMethod which dependent on class NetworkClass which calls via AFNetworking to a server, and return its response via block. Translating to code:
#interface TestedClass : NSObject
-(void)testMethod;
#end
-(void)testMethod
{
NetworkClass *networkClass = [[NetworkClass alloc] init];
[networkClass networkMethod:^(id result)
{
// code that I want to test according to the block given which I want to stub
...
}];
}
typedef void (^NetworkClassCallback)(id result);
#interface NetworkClass : NSObject
-(void)networkMethod:(NetworkClassCallback)handler;
#end
-(void) networkMethod:(NetworkClassCallback)handler
{
NSDictionary *params = #{#"param":#", #"value"};
NSString *requestURL = [NSString stringWithFormat:#"www.someserver.com"];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURLURLWithString:requestURL]];
NSURLRequest *request = [httpClient requestWithMethod:#"GET" path:requestURL parameters:params];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
handler(responseObject);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
handler(nil);
}];
[operation start];
}
How can I use Kiwi to stub networkMethod with block in order to unit test testMethod?
UPDATE: Found how to do this in Kiwi, see my answer below.
Here is how you do this in Kiwi:
First, you must dependency inject NetworkClass to TestedClass (if it's not clear how, please add a comment and I'll explain; this can be done as a property for simplicity. This is so that you can operate on a mock object for the NetworkClass)
Then your spec, create the mock for the network class and create your class that you want to unit test:
SPEC_BEGIN(TestSpec)
describe(#"describe goes here", ^{
it(#"should test block", ^{
NetworkClass *mockNetworkClass = [NetworkClass mock];
KWCaptureSpy *spy = [mockNetworkClass captureArgument:#selector(networkMethod:) atIndex:0];
TestedClass testClass = [TestedClass alloc] init];
testClass.networkClass = mockNetworkClass;
[testClass testMethod];
NetworkClassCallback blockToRun = spy.argument;
blockToRun(nil);
// add expectations here
});
});
SPEC_END
To explain what's going on here:
You are creating TestedClass and calling testMethod. However, before that, we are creating something called Spy - its job is to capture the block in the first parameter when networkMethod: is called. Now, it's time to actually execute the block itself.
It's easy to be confused here so I'll emphasize this: the order of calls is important; you first declare the spy, then call the tested method, and only then you're actually calling and executing the block!
This will give you the ability to check what you want as you're the one executing the block.
Hope it helps for other, as it took me quite sometime to understand this flow.

OCMock of class with method called from the test method

I am trying to test a method that instantiates an instance of MFMailComposeViewController. The method being tested calls several methods of MFMailComposeViewController including setSubject:.
I want to test that setSubject is sent a specific NSString, in this case #"Test Message".
No matter what I specify for the expected string in the mock stub there is no failure.
In the Unit Test class:
#import <OCMock/OCMock.h>
- (void)testEmail {
TestClass *testInstance = [[TestClass alloc] init];
id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
[[mock stub] setSubject:#"Test Message"];
[testInstance testMethod];
}
In TestClass:
- (void)testMethod {
MFMailComposeViewController *mailComposeVC = [[MFMailComposeViewController alloc] init];
[mailComposeVC setSubject:#"Bad Message"];
}
Test Suite 'Email_Tests' started at 2011-09-17 18:12:21 +0000
Test Case '-[Email_Tests testEmail]' started.
Test Case '-[Email_Tests testEmail]' passed (0.041 seconds).
The test should have failed.
I am testing this in the iOS simulator and get the same result on a device.
What am I doing wrong? Is there some way to accomplish this?
You create a mock, but never pass it to the class under test, or ask the mock to verify itself. You need some form of dependency injection to say, "Instead of using MFMailComposeViewController, use this other thing I'm giving you."
Here's one way to do that. In the class under test, instead of allocating MFMailComposeViewController directly, get it through a factory method, like this:
#interface TestClass : NSObject
- (void)testMethod;
// Factory methods
+ (id)mailComposeViewController;
#end
Here's the implementation. You were leaking, so note that the factory method returns an autoreleased object.
- (void)testMethod {
MFMailComposeViewController *mailComposeVC =
[[self class] mailComposeViewController];
[mailComposeVC setSubject:#"Bad Message"];
}
+ (id)mailComposeViewController {
return [[[MFMailComposeViewController alloc] init] autorelease];
}
Over on the testing side, we create a testing subclass that overrides the factory method so it provides whatever we want it to:
#interface TestingTestClass : TestClass
#property(nonatomic, assign) id mockMailComposeViewController;
#end
#implementation TestingTestClass
#synthesize mockMailComposeViewController;
+ (id)mailComposeViewController {
return mockMailComposeViewController;
}
#end
Now we're ready for the test. I do a few things differently:
Allocate a testing subclass rather than the actual class (and don't leak!)
Set up the mock with an expectation, not just a stub
Inject the mock into the testing subclass
Verify the mock at the end
Here's the test:
- (void) testEmail {
TestClass *testInstance = [[[TestClass alloc] init] autorelease];
id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
[[mock expect] setSubject:#"Test Message"];
[testInstance setMockMailComposeViewController:mock];
[testInstance testMethod];
[mock verify];
}
For completeness, we need one final test, and that's to guarantee that the factory method in the actual class returns what we expect:
- (void)testMailComposerViewControllerShouldBeCorrectType {
STAssertTrue([[TestClass mailComposeViewController]
isKindOfClass:[MFMailComposeViewController class]], nil);
}
Jon Reid's is a reasonable approach, though it seems that making mailComposeViewController a class method complicates it. And subclassing it in your test code means you'll always get the mocked version at test time, which may not be what you want. I'd make it an instance method. Then you can use a partial mock to override it at test time:
-(void) testEmail {
TestClass *testInstance = [[[TestClass alloc] init] autorelease];
id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
[[mock expect] setSubject:#"Test Message"];
id mockInstance = [OCMockObject partialMockForObject:testInstance];
[[[mockInstance stub] andReturn:mock] mailComposeViewController];
[testInstance testMethod];
[mock verify];
}
If you keep it as a class method, you might consider making it a static global and exposing a way to override it:
static MFMailComposeViewController *mailComposeViewController = nil;
-(id)mailComposeViewController {
if (!mailComposeViewController) {
mailComposeViewController = [[MFMailComposeViewController alloc] init];
}
return mailComposeViewController;
}
-(void)setMailComposeViewController:(MFMailComposeViewController *)controller {
mailComposeViewController = controller;
}
Then, your test would be similar to Jon's example:
-(void)testEmail {
TestClass *testInstance = [[[TestClass alloc] init] autorelease];
id mock = [OCMockObject mockForClass:[MFMailComposeViewController class]];
[[mock expect] setSubject:#"Test Message"];
[testInstance setMailComposeViewController:mock];
[testInstance testMethod];
[mock verify];
// clean up
[testInstance setMailComposeViewController:nil];
}

How to init an object with stubbed values with OCMock

Ho do I stub a method used in the init method?
The related methods in my class:
- (id)init
{
self = [super init];
if (self) {
if (self.adConfigurationType == AdConfigurationTypeDouble) {
[self configureForDoubleConfiguration];
}
else {
[self configureForSingleConfiguration];
}
}
return self;
}
- (AdConfigurationType)adConfigurationType
{
if (adConfigurationType == NSNotFound) {
if ((random()%2)==1) {
adConfigurationType = AdConfigurationTypeSingle;
}
else {
adConfigurationType = AdConfigurationTypeDouble;
}
}
return adConfigurationType;
}
My test:
- (void)testDoubleConfigurationLayout
{
id mockController = [OCMockObject mockForClass:[AdViewController class]];
AdConfigurationType type = AdConfigurationTypeDouble;
[[[mockController stub] andReturnValue:OCMOCK_VALUE(type)] adConfigurationType];
id controller = [mockController init];
STAssertNotNil([controller smallAdRight], #"Expected a value here");
STAssertNotNil([controller smallAdRight], #"Expected a value here");
STAssertNil([controller largeAd], #"Expected nil here");
}
My result:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'OCMockObject[AdViewController]: unexpected method invoked: smallAdRight '
So how will I access the AdViewController in the OCMockObject?
If you use the mockForClass: method you will need to provided stubbed implementations for each and every method that are called in the mocked class. including your call into it with [controller smallAdRight] in your first test.
Instead you can use the niceMockForClass: method which will just ignore any messages which are not mocked.
Another alternative is to instantiate your AdViewController and then create a partial mock for it using the partialMockForObject: method. This way the internals of the controller class will do the main part of the work.
Just a though... are you trying to test the AdViewController or a class which uses it? It appears that you are trying to mock the entire class and then test if it still behaves normally. If you want to test that AdViewController behaves as expected when certain values are injected then your best option is most likely the partialMockForObject: method:
- (void)testDoubleConfigurationLayout {
AdViewController *controller = [AdViewController alloc];
id mock = [OCMockObject partialMockForObject:controller];
AdConfigurationType type = AdConfigurationTypeDouble;
[[[mock stub] andReturnValue:OCMOCK_VALUE(type)] adConfigurationType];
// You'll want to call init after the object have been stubbed
[controller init]
STAssertNotNil([controller smallAdRight], #"Expected a value here");
STAssertNotNil([controller smallAdRight], #"Expected a value here");
STAssertNil([controller largeAd], #"Expected nil here");
}