So I have a class I wrote some test cases for. This class has these two methods:
- (void)showNextNewsItem {
self.xmlUrl = self.nextNewsUrl;
[self loadWebViewContent];
}
- (void)showPreviousNewsItem {
self.xmlUrl = self.previousNewsUrl;
[self loadWebViewContent];
}
Could be refactored and this is quite primitive, but nevertheless I just want to make sure next loads next and previous loads previous. So I use OCMock to instantiate a OCMockObject for my SUT class like this:
- (void)testShowNextOrPreviousItemShouldReloadWebView {
id mockSut = [OCMockObject mockForClass:[NewsItemDetailsViewController class]];
[[[mockSut expect] andReturn:#"http://www.someurl.com"] nextNewsUrl];
[[mockSut expect] loadWebViewContent];
[[[mockSut expect] andReturn:#"http://www.someurl.com"] previousNewsUrl];
[[mockSut expect] loadWebViewContent];
[mockSut showNextNewsItem];
[mockSut showPreviousNewsItem];
[mockSut verify];
}
The problem lies in the two lines actually calling the methods that do something to be verified. OCMock now tells me, that invoking showNextNewsItem and showPreviousNewsItem are not expected. Of course, it's not expected because I am in the test and I only expect certain things to happen in the production code itself.
Which part of the mocking concept didn't I understand properly?
It's generally confusing to mock the class under test, but if you want to do that, you need a "partial mock", so that you can call methods without stubbing them and have them execute the normal methods.
This appears to be supported in OCMock according to the docs.
I found the solution. Using a partialMock on the object does exactly what I want. This way, calls I explicitly define are mocked and I call the methods that are under test on the "not-mocked" object.
- (void)testShowNextOrPreviousItemShouldReloadWebView {
NewsItemDetailsViewController *sut = [[NewsItemDetailsViewController alloc] init];
id mockSut = [OCMockObject partialMockForObject:sut];
[[[mockSut expect] andReturn:#"http://www.someurl.com"] nextNewsUrl];
[[mockSut expect] loadWebViewContent];
[[[mockSut expect] andReturn:#"http://www.someurl.com"] previousNewsUrl];
[[mockSut expect] loadWebViewContent];
[sut showNextNewsItem];
[sut showPreviousNewsItem];
[mockSut verify];
}
Related
I have a class that has only class methods
//FileManager.h
+(void)writeData:(NSData*)data toFile:(NSString*)filePath;
//...
//some more class methods
I use this class inside another one
//AttachmentsRepository.m
-(void)newAttachment:(NSData*)attachmentData{
//some code
NSString * generatedPath = #"/some/generated/path";
[FileManager writeData:attachmentData toFile:generatedPath];
}
Now I am writing tests for AttachmentRepository and was wondering how to inject FileManager.
What I use is the "extract and override call" technique:
//AttachmentsRepository.m
-(void)newAttachment:(NSData*)attachmentData{
//some code
NSString * generatedPath = #"/some/generated/path";
[[self getFileManagerClass] writeData:attachmentData toFile:generatedPath];
}
-(Class)getFileManagerClass{
return [FileManager class];
}
Now in my test I just stub out getFileManagerClass to return [FakeFileManager class]
What is a better solution? This is legacy code but lets say that we are writing it now. How should the FileManager dependency be implemented.
PS. I want to use a fake because I would like to avoid writing to disk when testing the AttachmentRepository class.
From "Working Effectively With Legacy Code" by Michael C. Feathers I found out about the "Introduce Instance Delegator" technique.
Basically we duplicate the methods we need and make them instance methods. Their body is just a call to the class/static method. Then we use the instance class as argument to the constructor or the method that needs to use it.
//FileManager.h
+(void)writeData:(NSData*)data toFile:(NSString*)filePath;
-(void)writeData:(NSData*)data toFile:(NSString*)filePath;
//...
//FileManager.m
-(void)writeData:(NSData*)data toFile:(NSString*)filePath;{
[FileManager writeData:data toFile:filePath];
}
In AttachmentsRepository:
//AttachmentsRepository.m
-(instance) initWithFileManager:(FileManager *)fileManager{
if(self = [super init]){
_fileManager = fileManager
}
return self;
}
-(void)newAttachment:(NSData*)attachmentData{
//some code
NSString * generatedPath = #"/some/generated/path";
[self.fileManager writeData:attachmentData toFile:generatedPath];
}
The conclusion seems to be that the FileManager class shouldn't have had class/static methods but instance methods instead when it was implemented at the start.
All this kinda makes me feel awkward - making methods that can be class/static to be instead on instance level.
I have a method I would like to test with OCMock but not sure how to do that. I need to mock
ExtClass which isn't defined as part of my code (external library):
+(NSString *)foo:(NSString *)param
{
ExtClass *ext = [[ExtClass alloc] initWithParam:param];
if ([ext someMethod])
return #"A";
else
return #"B";
}
Thanks in advance!
OCMock 2
id mock = [OCMockObject mockForClass:[ExtClass class]];
// We stub someMethod
BOOL returnedValue = YES;
[[[mock stub] andReturnValue:OCMOCK_VALUE(returnedValue)] someMethod];
// Here we stub the alloc class method **
[[[mock stub] andReturn:mock] alloc];
// And we stub initWithParam: passing the param we will pass to the method to test
NSString *param = #"someParam";
[[[mock stub] andReturn:mock] initWithParam:param];
// Here we call the method to test and we would do an assertion of its returned value...
[YourClassToTest foo:param];
OCMock3
// Parameter
NSURL *url = [NSURL URLWithString:#"http://testURL.com"];
// Set up the class to mock `alloc` and `init...`
id mockController = OCMClassMock([WebAuthViewController class]);
OCMStub([mockController alloc]).andReturn(mockController);
OCMStub([mockController initWithAuthenticationToken:OCMOCK_ANY authConfig:OCMOCK_ANY]).andReturn(mockController);
// Expect the method that needs to be called correctly
OCMExpect([mockController handleAuthResponseWithURL:url]);
// Call the method which does the work
[self.myClassInstance authStarted];
OCMVerifyAll(mockController);
Notes
Ensure that in both cases you stub two methods (alloc and the init... method). Also, make sure that both stubbing calls are made on the instance of the class mock (not the class itself).
Docs: Class methods section in the OCMock features
Alternatives
This (strange)solution may be useful in case you want to test legacy code that due to whatever reason you cannot refactor. However, if you can modify the code you should refactor it and get an ExtClass object as a parameter, not a string, delegating the creation of ExtClass out of that method. Your production and test code would be simpler and clearer, specially in a more complex real life case, not in this simple example.
in my fixture setUp i have the following
-(void)setUp{
_vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];
//stub out the view stuff
[[_vc stub] removeTask:OCMOCK_ANY];
[[_vc stub] insertTask:OCMOCK_ANY];
}
There are 15 tests in the fixture, however, I need to actually test that those 2 methods are invoked, so I wrote 2 tests
-(void)someTest{
[[_vc expect] removeTask:OCMOCK_ANY];
[_vc removeAllTasksFromList:taskList notInList:newTaskList];
[_vc verify];
}
but that test fails
i also tried
-(void)someTest{
[[_vc stopMocking];
[[_vc expect] removeTask:OCMOCK_ANY];
[[_vc stub] removeTask:OCMOCK_ANY];
[_vc removeAllTasksFromList:taskList notInList:newTaskList];
[_vc verify];
}
But the test still fails. Am I missing something, or is this just how OCMock works?
The only way I can make it work is like this
-(void)someTest{
//re create and init the mock object
_vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];
[[_vc expect] removeTask:OCMOCK_ANY];
[[_vc stub] removeTask:OCMOCK_ANY];
[_vc removeAllTasksFromList:taskList notInList:newTaskList];
[_vc verify];
}
Maybe the documentation should be clearer. What stopMocking does for a partial mock is to restore the real object, in your case the InboxViewController, to its original state. Calling stopMocking does not reset the mock object, which means it does not clear the stubs and expectations. You can always call stopMocking and then create a new mock for the same real object.
As was pointed out in another answer, stubbing and expecting the same method is generally better avoided, but if you have to do it, make sure to set up the expect before the stub; otherwise the stub will handle the invocations and the expect will never see them.
I know that traditionally many people recommend to use the setup method to set up the test subject. My personal experience, over the years, is that it's generally not worth it. Saving a couple of lines in each test might look attractive but in the end it does create coupling between the individual tests, making the suite more brittle.
Seems that you need to create a new mock to expect a method that you already stubbed.
I would recommend you to reconsider to use a partial mock in all your test cases, and if you want so, extract this:
[[_vc stub] removeTask:OCMOCK_ANY];
[[_vc stub] insertTask:OCMOCK_ANY];
into a helper method and call that method from the tests you really need it, removing it from your setUp method.
And, small tip :), you should call [super setUp] at the beginning of your setUp implementation.
You can do the following to remove stubs from an OCMockObject, which will enable you to keep the stub code in your -(void)setUp, but still allow you to add an expect in a later test.
Add the following category to your test to return the OCMockObject ivar.
#interface OCMockObject (Custom)
- (void)removeStubWithName:(NSString*)stubName;
#end
#implementation OCMockObject (Custom)
- (void)removeStubWithName:(NSString*)stubName {
NSMutableArray* recordersToRemove = [NSMutableArray array];
for (id rec in recorders) {
NSRange range = [[rec description] rangeOfString:stubName];
if (NSNotFound == range.location) continue;
[recordersToRemove addObject:rec];
}
[recorders removeObjectsInArray:recordersToRemove];
}
#end
Example usage:
Make sure you remove the stub for a method BEFORE you add the expect.
-(void)someTest{
[_vc removeStubWithName:#"removeTask"];
[[_vc expect] removeTask:OCMOCK_ANY];
[_vc removeAllTasksFromList:taskList notInList:newTaskList];
[_vc verify];
}
This will allow your test to run as you expect.
It's a useful hack, at least until the OCMock developers allow this functionality.
A couple of things to note:
You should keep a reference to the actual object you're mocking. Invoke mock setup/verification on the mock reference, and methods you're testing on the actual object.
You shouldn't stub and expect the same method call. The stub will match, and the expectation won't pass.
I'm assuming you're stubbing in setUp because you have some methods that may or may not be called in the tests. If so, you can structure your tests like this:
static InboxViewController *_vc;
static id mockInbox;
-(void)setUp{
_vc = [[InboxViewController alloc] init];
mockInbox = [OCMockObject partialMockForObject:_vc];
//stub out the view stuff
[[mockInbox stub] removeTask:OCMOCK_ANY];
[[mockInbox stub] insertTask:OCMOCK_ANY];
}
-(void)someTest{
[[mockInbox expect] somethingIExpectForThisTest:OCMOCK_ANY];
[_vc removeAllTasksFromList:taskList notInList:newTaskList];
[mockInbox verify];
}
-(void)someOtherTest{
[[mockInbox expect] someOtherThingIExpectForThisTest:OCMOCK_ANY];
[_vc doSomethingElse];
[mockInbox verify];
}
The order in which the stub and expect calls are given is important to the partial mock. Stubbing the method before expecting it will mean that any messages sent to the stubbed method will never meet the expectation.
On the other hand, stopMocking just means the partial mock will stop being associated to the real object. The mock still keeps its recorders (stubs and expectations) and works as usual. You can verify this by sending removeAllTasksFromList:notInList to your real object, which in this case is not assigned to any variable. You'll see in this case that the message does reach your object's implementation. The mock will still fail the verify call, though. In general, you should be calling methods on your real object. The partial mock will still intercept the messages.
As mentioned in the other answer, the best way around this is to create a new partial mock and call expect before stubbing the method. You could even implement a helper:
- (void) setUpPartialMockWithExpect:(BOOL)needExpect {
_vc = [OCMockObject partialMockForObject:[[InboxViewController alloc] init]];
if( needExpect )
[[_vc expect] removeTask:OCMOCK_ANY];
//stub out the view stuff
[[_vc stub] removeTask:OCMOCK_ANY];
[[_vc stub] insertTask:OCMOCK_ANY];
}
And call that in each of your -(void)test...
I'm trying to mock a call to NSURLConnection. I swizzle the class method call with my own that calls my mock object. I'm trying to implement the mock like this:
[[[[urlConnectionMock expect] andDo:^(NSInvocation *invocation) {
NSURLResponse * __autoreleasing *responsePointer;
[invocation getArgument:&responsePointer atIndex:3];
id response = [OCMockObject niceMockForClass:NSHTTPURLResponse.class];
[[[response stub] andReturnValue:OCMOCK_VALUE((NSInteger){ 200 })] statusCode];
*responsePointer = response;
}] andReturn:encryptedResponse] sendSynchronousRequest:OCMOCK_ANY returningResponse:OCMOCK_ANY error:OCMOCK_ANY];
The problem is that OCMOCK_ANY is an Objective-C pointer, not a pointer to a pointer (the compiler rightly complains). Before I dig more deeply into the OCMock source, perhaps someone can suggest how to get around this? Full credit given if you have a better approach entirely.
Figured it out. By-reference is already built into OCMock.
id fakeResponse = [OCMockObject niceMockForClass:NSHTTPURLResponse.class];
[[[fakeResponse stub] andReturnValue:OCMOCK_VALUE((NSInteger){ 200 })] statusCode];
[[[urlConnectionMock expect] andReturn:encryptedResponse] sendSynchronousRequest:OCMOCK_ANY returningResponse:[OCMArg setTo:fakeResponse] error:[OCMArg setTo:nil]];
This is so much happier looking as well!
Although I have seen similar questions in SO, none of the answers seems to solve my problem.
I have a NSManagedObject class generated by mogenerator with custom functions (not in the model):
#interface MyManagedClass : _MyManagedClass {
-(NSNumber*)getRandomNumber2;
-(void)function_I_want_to_test;
}
My function_I_want_to_test() depend on the result of random(), and that is something I must control during testing. Since I cannot mock random() I built a function wrapper, which by the way, is not static because I had many problems with OCMock and static class functions.
The setup of my unit test looks like:
[MagicalRecord setDefaultModelFromClass:[self class]];
[MagicalRecord setupCoreDataStackWithInMemoryStore];
Using the debugger I could verify that the model is properly loaded. Also if I do it the non magical way:
NSBundle *b = [NSBundle bundleForClass:[self class]];
model = [NSManagedObjectModel mergedModelFromBundles:#[b]];
After this point I cannot create any mock to stub my random() wrapper function
I have tried a class mock
id mock = [OCMockObject mockForClass:[MyManagedClass class]];
[[[mock stub] andReturn:#50] getRandomNumber2];
MyManagedClass *my_object = [mock MR_createEntity];
I have tried using a partial mock
MyManagedClass *my_object = [MyManagedClass MR_createEntity];
id mock2 = [OCMockObject partialMockForObject:my_object];
After last point, just creating an instance of mock2 destroys the dynamic properties of my_object, and it become useless.
I have also tried using a mock protocol with the function I want to stub, still to no avail.
The runtime exception is the normal one other people get when using with tests with Core Data objects: the properties are not recognized selectors.
However the strange thing to me is that I am not trying to stub any dynamic property, but a normal, compile time known function. Hence it seems strange to me that using OCMock renders my instances useless.
Ideally I would like something that uses OCMock/Mogenerator/Magicalrecord.
what am I doing wrong?
I recommend against trying to mock out managed objects. There's a lot of runtime craziness going on to make managed objects work. That's why I suggest going with an in memory database approach for testing. This will let you create empty instances of your entities while letting the core data stuff happen.
Since you're probably using unit tests, I suggest that in each test case where you think you need to mock out some data to instead recreate your whole stack, and set a new entity up with the state it needs to run your test. You can also make a test in memory persistent store separate from the one the default stack create method gives you, and attach this second one to your default stack. That is, make a new in memory store, initialize it with your fake/mock data entities, and attach that to your test data stack.
I hope this rambling helps a bit, but the bottom line is dont mock managed objects...really.
You could do it by moving your random number generation out of the Core Data object and into a helper class:
#implementation RandomNumberGenerator
static RandomNumberGenerator *_sharedInstance = nil;
+(RandomNumberGenerator *)sharedInstance {
if (_sharedInstance == nil) {
_sharedInstance = [[RandomNumberGenerator alloc] init];
}
return _sharedInstance;
}
+(void)setSharedInstance:(RandomNumberGenerator *)instance {
[instance retain];
[_sharedInstance release];
_sharedInstance = instance;
}
-(NSNumber *)generateRandomNumber {
return ...
}
#end
Then in your test:
-(void)testSomething {
id mockGenerator = [OCMockObject mockForClass:[RandomNumberGenerator class]];
[RandomNumberGenerator setSharedInstance:mockGenerator];
[[[mockGenerator stub] andReturn:#(4)] generateRandomNumber];
MyManagedClass *my_object = [MyManagedClass MR_createEntity];
expect(my_object.someField).to.equal(someValueBasedOnGeneratedRandomNumber);
}