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