The short answer is that accessing a static variable from another file is not possible. This is exactly the same problem as trying to refer to a local function variable from another place; the name is simply unavailable. In C there are three stages of "visibility" for objects *, which are called "binding": external (global), internal (limited to one "translation unit" - free, one file) and "no" (local function). When you declare a variable as static , it gives an internal binding; no other file can access it by name. You need to make some kind of access function to expose it.
The extended answer is that since there is some streamlining of the ObjC library that we can do in any case to simulate class-level variables, we can make a somewhat generalized test code that you can conditionally compile. However, this is not particularly clear.
Before we begin, we note that this still requires an individual implementation of one method; There is no way around this due to communication restrictions.
Step one, declare methods, one to configure, and then set for valueForKey: -like access:
// ClassVariablesExposer.h #if UNIT_TESTING #import <Foundation/Foundation.h>
These methods are semantically more like a protocol than a category. The first method must be overridden in each subclass, because the variables you want to bind will of course be different, and because of the binding problem. The actual call to objc_setAssociatedObject() , where you are referencing the variable, must be in the file in which the variable is declared.
However, using this method in the protocol will require an additional header for your class, because although the implementation of the protocol method should go in the main implementation file, ARC and your unit tests should see a declaration that your class complies with the protocol. Bulky. Of course, you can make this NSObject category compatible with the protocol, but then you will need a stub anyway to avoid the "incomplete implementation" warning. I did each of these steps in developing this solution and decided that they were not needed.
The second set, accessors, work perfectly as category methods, because they look like this:
// ClassVariablesExposer.m #import "ClassVariablesExposer.h" #if UNIT_TESTING @implementation NSObject (ClassVariablesExposer) + (void)associateClassVariablesByName { // Stub to prevent warning about incomplete implementation. } + (id)classValueForName:(char *)name { return objc_getAssociatedObject(self, name); } + (BOOL)classBOOLForName:(char *)name { NSValue * v = [self classValueForName:name]; BOOL * vp = [v pointerValue]; return *vp; } @end #endif /* UNIT_TESTING */
Completely general, although their successful use depends on your use of macros from above.
Then define your class by overriding this setting method to capture class variables:
// Milliner.h
// Milliner.m
Make sure your unit test file imports the title for the category. A simple demonstration of this functionality:
#import <Foundation/Foundation.h> #import "Milliner.h" #import "ClassVariablesExposer.h" #define BOOLToNSString(b) (b) ? @"YES" : @"NO" int main(int argc, const char * argv[]) { @autoreleasepool { [Milliner associateClassVariablesByName]; NSString * actualFeatherType = [Milliner classValueForName:"featherType"]; NSLog(@"Assert [[Milliner featherType] isEqualToString:@\"chicken hawk\"]: %@", BOOLToNSString([actualFeatherType isEqualToString:@"chicken hawk"])); // Since we got a pointer to the BOOL, this does track its value. NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"])); [Milliner flipWaterproof]; NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"])); } return 0; }
I put the project on GitHub: https://github.com/woolsweater/ExposingClassVariablesForTesting
Another caveat is that for each type of POD you want to access, you will need its own method: classIntForName: classCharForName: etc.
Although it works, and I always enjoy the monkey with ObjC, I think it might be too smart in half; if you have only one or two of these class variables, the simplest suggestion is to conditionally compile accessors for them (make an Xcode code fragment). My code here will probably only save you time and effort if you have many variables in the same class.
However, perhaps you can benefit from this. Hope this was fun to read, at least.
* The meaning is simply βthe thing that the linker knowsβ - function, variable, structure, etc. - not in the feelings of ObjC or C ++.