4

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?

Leem.fin
  • 40,781
  • 83
  • 202
  • 354
  • `-(int)getData() {` – Ozgur Vatansever Sep 20 '16 at 03:27
  • No, if I change to `-(int)getData(){`, I get weird compiler error `expect function body`, my current code works just fine, the mocked function is invoked by production code. The problem comes only when I assign value to mData inside mocked function. Again, it is a C function. – Leem.fin Sep 20 '16 at 03:30
  • 1
    I understand what you meant, and I tried, and then I wrote the above comment. – Leem.fin Sep 20 '16 at 03:35
  • But you can't define a c function as an instance method. `mData` is an instance variable so you should access it via `self` when you want to assign a new value to it. Maybe you might want to take a look at [this SO question](http://stackoverflow.com/questions/801976/mixing-c-functions-in-an-objective-c-class). – Ozgur Vatansever Sep 20 '16 at 03:37
  • Thanks, I also tried `self.mData = 10`, same error. Yep, I guess you are right that C function is not considered as a instance function. My main point of posting this question is to find a solution for it so that I can assign value to a instance variable inside the C function. Currently, I found one way is to define it above `@interface StoreTests : XCTestCase` – Leem.fin Sep 20 '16 at 03:46

2 Answers2

1

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

CRD
  • 52,522
  • 5
  • 70
  • 86
0

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
...
Leem.fin
  • 40,781
  • 83
  • 202
  • 354