This question already has an answer here:
How to mock an object with OCMock which isn't passed as a parameter to method?
(1 answer)
Closed 6 years ago.
I am using OCMock 3 to write my unite tests in iOS project.
I have a foo method under School class:
#implementation School
-(NSString *)foo:
{
// I need to mock this MyService instance in my test
MyService *service = [[MyService alloc] init];
// I need to stub the function return for [service getStudent]
Student *student = [service getStudent];
if (student.age == 12) {
//log the age is 12
} else {
//log the age is not 12
}
...
}
The Student looks like this:
#interface Student : NSObject
#property NSInteger age;
...
#end
In my test case, I want to stub the method call [service getStudent] to return a Student instance with age value 12 I defined:
// use mocked service
id mockService = OCMClassMock([MyService class]);
OCMStub([[mockService alloc] init]).andReturn(mockService);
// create a student instance (with age=12) which I want to return by function '-(Student*) getStudent:'
Student *myStudent = [[Student alloc] init];
myStudent.age = 12;
// stub function to return 'myStudent'
OCMStub([mockService getStudent]).andReturn(myStudent);
// execute foo method
id schoolToTest = [OCMockObject partialMockForObject:[[School alloc] init]];
[schoolToTest foo];
When I run my test case, however, the student returned by -(Student*)getStudent: method is not with age 12, why?
===== UPDATE ====
I noticed in internet, somebody suggested to separate the alloc and init to stub. I also tried it but it doesn't work as it says:
// use mocked service
id mockService = OCMClassMock([MyService class]);
OCMStub([mockService alloc]).andReturn(mockService);
OCMStub([mockService init]).andReturn(mockService);
// stub function to return 'myStudent'
OCMStub([mockService getStudent]).andReturn(myStudent);
When I do this & run my test case, the real implementation of -(Student*)getStudent: method get called... I can't understand why people says it works.
You cannot mock the init method. This is stated in the documentation (Section 9.3), but maybe it's too hidden.
Related
Why can self be used in implementation methods, or in class methods? What's the internal structure of self? eg:
#import "Person.h"
#implementation Person
+ (void)initialize
{
[super initialize];
[self setupModel];
}
- (instancetype)init
{
self = [super init];
if (self) {
[self test];
}
return self;
}
+ (void)setupModel
{
NSLog(#"setup a person object");
}
- (void)test
{
NSLog(#"this method is called");
}
#end
Code run results:
2018-09-17 09:32:40.615578+0800 Test[662:301213] setup a person object
2018-09-17 09:32:40.615662+0800 Test[662:301213] this method is called
Code run results
For instance methods, self is a reference to the current object.
For class methods, self is a reference to the current class.
If you declare an Objective-C method like this
- (void)doSomethingWithObject:(id)object { ... }
the compiler basically just turns it into a a C function with the following signature:
void _i_classname_doSomethingWithObject_(const id self, const SEL _cmd, id object) { ... }
(where classname is the name of the class the method belongs to. for class methods, the i becomes a c)
When you call the method [foo doSomethingWithObject:bar], the compiler turns that method call into a call to objc_msgSend, which "forwards" the call to the correct implementation:
objc_msgSend(foo, #selector(doSomethingWithObject:), bar);
As you can see, the parameters passed to objc_megSend are the same expected by the C function containing the implementation of the Objective-C method.
These are implementation details, so you shouldn't rely on them:
self is either a tagged pointer or a genuine pointer to an struct that starts with the isa field and then contains all of the object's instance variables.
The isa field is itself either a set of bitfields that contains the instance's retain contain, some flags, and a pointer to the Class of the instance, or (on older and 32-bit platforms) it is just a pointer to the Class of the instance (in which case the retain count is stored in a global hash table).
My code invokes a C library function:
#implementation Store
...
-(void) doWork {
// this is a C function from a library
int data = getData();
...
}
end
I am unit testing the above function, I want to mock the C function getData() in my test, here is my test case:
#interface StoreTests : XCTestCase {
int mData;
Store *store;
}
#end
#implementation StoreTests
-(void) setUp {
[super setUp];
mData = 0;
store = [[Store alloc] init];
}
-(void) testDoWork {
// this call will use the mocked getData(), no problem here.
[store doWork];
}
// mocked getData()
int getData() {
mData = 10; // Use of undeclared identifier 'mData', why?
return mData;
}
...
#end
Why I get complier error:
Use of undeclared identifier 'mData' inside mocked getData() function?
You are misunderstanding how instance methods and variables work.
Every instance method has a variable self which references the current instance (or "current object") and a use of an instance variable, such as mData, is shorthand for accessing that variable using self, e.g self->mData, where -> is the (Objective-)C operator for field access. So your setup method written "long hand" is:
-(void) setUp {
[super setUp];
self->mData = 0;
self->store = [[Store alloc] init];
}
But where does self, the reference to the instance, itself come from? Well it's not magical, just hidden, it is passed to an instance method automatically as a hidden extra argument. At this point which switch to pseudo-code to show this. Your setup method is effectively compiled as:
-(void) setUp withSelf:(StoreTest *)self {
[super setUp];
self->mData = 0;
self->store = [[Store alloc] init];
}
and a call such as:
StoreTests *myStoreTests = ...
[myStoreTests setup];
is effectively compiled as something like:
[myStoreTests setup withSelf:myStoreTests];
automatically adding the extra self argument.
Now all the above only applies to methods, and enables them to access instance variables and methods, it does not apply to plain C functions - they have no hidden self argument and cannot access instance variables.
The solution you mention in the answer you added of declaring mData outside of the interface:
int mData;
#interface StoreTests : XCTestCase {
Store *store;
}
#end
changes mData into a global variable, instead of being an instance variable. C functions can access global variables. However this does mean that every instance of the class shares the same mData, there is only one mData in this case rather than one for every instance.
Making an instance variable into a global is therefore not a general solution to to issues like this, however as it is unlikely that you will have more than one instance of your StoreTests class it is a suitable solution in this case.
You should however make one change: you can only have one global variable with a given name with a program, so your mData must be unique and is accessible by any code within your program, not just the code of StoreTests. You can mitigate this my declaring the variable as static:
static int mData;
this keeps the variable as global but only makes it visible to code within the same file as the declaration, which is probably just the code of StoreTests.
HTH
I found one solution for my question, that is declare mData above #interface StoreTests : XCTestCase, something like this:
int mData;
#interface StoreTests : XCTestCase {
Store *store;
}
#end
...
I am using OCMock v3 do unit testing, I want to test the following piece of code:
#implementation School
-(void) handleStudent:(Student*) student{
Bool result = [self checkIdentityWithName:student.name age:student.age];
...
}
...
#end
In my following test case I created a student instance with name "John", age 23. and then I run the function under test:
-(void) testHandleStudent{
Student *student = [Student initWithName:#"John" age:23];
// function under test
[schoolPartialMock handleStudent:student];
// I want to not only verify checkIdentityWithName:age: get called,
// but also check the exact argument is passed in. that's John 23 in this case
// how to check argument ?
}
In my test case, I want to verify that the exact arguments values are passed into function checkIdentityWithName:age: . that's name "John" and age 23 are used. How to verify that in OCMock v3? (There is no clear example in its documentation how to do it.)
You can make it like that
-(void) testHandleStudent{
id studentMock = OCMClassMock([Student class]);
OCMStub([studentMock name]).andReturn(#"John");
OCMStub([studentMock age]).andReturn(23);
[schoolPartialMock handleStudent:studentMock];
OCMVerify([schoolPartialMock checkIdentityWithName:#"John" age:23]);
}
or
-(void) testHandleStudent{
id studentMock = OCMClassMock([Student class]);
OCMStub([studentMock name]).andReturn(#"John");
OCMStub([studentMock age]).andReturn(23);
OCMExpect([schoolPartialMock checkIdentityWithName:#"John" age:23]);
[schoolPartialMock handleStudent:studentMock];
OCMVerifyAll(schoolPartialMock);
}
Hope this help
I have a simple School class which defines a init method:
#implementation School
- (id) init {
self = [super init];
if (self) {
// call class method of MyHelper class
if ([MyHelper isWeekend]) {
[MyHelper doSomething];
}
}
}
#end
(MyHelper is a class contains only class methods, the isWeekend is a class method returns a boolean value)
I use OCMock to unit test this simple init method:
- (void)testInit {
// mock a school instance
id schoolMock = [OCMockObject partialMockForObject:[[School alloc] init]];
// mock class MyHelper
id MyHelperMock = OCMStrictClassMock([MyHelper class]);
// stub class method 'isWeekend()' to return true
OCMExpect([MyHelperMock isWeekend]).andReturn(true);
// run init method
[schoolMock init];
// verify
OCMVerify([MyHelperMock isWeekend]);
}
But when run it, I get error:
OCMockObject(MyHelper): Method isWeekend was not invoked. why?
You've created a mock for the MyHelper class, but this isn't going to be used within the implementation of your School object. You'd only get the mocked response if you wrote [MyHelperMock isWeekend], which you can't do inside the initialiser without rewriting it for tests.
To make your School class more testable you should be passing in any dependencies on initialisation. For example, you could pass in the isWeekend value as part of the initialiser, instead of obtaining it inside the method, or pass in the class object (MyHelper or MyHelperMock).
It's worth noting that finding certain classes or methods difficult to test because of things like this is often a good indicator that your code isn't structured very well.
main principles:
object could be created through class method by providing unique identifier (whatever)
if object with given identifier doesn't exists, returned new object otherwise returned existing one
class guarantees that ONLY ONE object with given identifier could exist (sort of internal singleton)
So main point in keeping objects with unique filed (f.e id) for future using, since they might have own states (f.e loading, loaded so on) we are allowed to use it everywhere we need it without re-creating.
Is it design pattern?
F.e:
Advirtisement.h
#interface Advertisment : NSObject
+ (instancetype)adWithID:(NSString *)adID;
+ (NSMutableArray *)sharedAds;
Advertisement.m
+ (instancetype)adWithID:(NSString *)adID {
NSMutableArray *ads = [[self class] sharedAds];
// Look for existing ad based on the id
Advertisement *returnableAd = nil;
for (Advertisement *currentAd in ads) {
if ([currentAd.adID isEqualToString:adID]) {
returnableAd = currentAd;
break;
}
}
// Create a new ad instance for this id if one doesn't already exist.
if (!returnableAd) {
returnableAd = [[[self class] alloc] initWithID:adID];
[ads addObject:returnableAd];
}
return returnableAd;
}
}
+ (NSMutableArray *)sharedAds
{
static NSMutableArray *sharedAds;
if (!sharedAds) {
sharedAds = [NSMutableArray array];
}
return sharedAds;
}
It's Lazy Initialization - and some would also consider it an instance of a Factory Pattern (others would argue that in itself a factory doesn't constitute a pattern)