Alex Egli
Alex Egli

Reputation: 2064

How do I expose private properties in an iOS unit test?

I am trying to test a method of a class that uses some of the class' private properties. I looked around for best practices on how to access them in a unit test and decided on adding a category for the class in my test implementation file. It still won't let me access them though, I get "unrecgonized selector sent to instance" errors.

My implementation class properties definition (at the top of the ImplementationClass.m file):

@interface ImplementationClass () {
    TWPLoadUnitEnum loadUnit;
    BOOL recosMatchTargets;
}

The category at the top of my test class:

//interface to expose private variables of test case class
@interface ImplementationClass (Test)
    @property(readwrite,assign) TWPLoadUnitEnum loadUnit;
    @property(readwrite,assign) BOOL recosMatchTargets;
@end

Where I access the properties:

        ImplementationClass *realRecosModal = [[ImplementationClass alloc] 
initWithNibName:@"ImplementationClassVC" bundle:nil];
        [realRecosModal setLoadUnit:TWPLoadUnitKg]

I get the error on the line where I call "setLoadUnit". Am I not exposing the private property correctly?

Upvotes: 3

Views: 2389

Answers (2)

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

I'm not sure if it is wise to let a unit test check how the object did do something private, because this would make it more difficult to change how a class performs its function.

If your function changes a private property, then this change should influence the behaviour of any of the other public properties / functions. If the value of this property does not influence any of the public functions, why use this property and why change it? Conclusion: if function A changes the value of a private property, then there should be a function B (which might be the same as A)that changes behaviour because of this change.

The specification of function A should not contain that it sets the private property x, but that it changes the behaviour of B:

Specification: If public A(int x) is called with parameter value x, then `public int B {get;} should return 2*x.

There are several methods to implement this. Your test should not test how it is implemented. It should test the specification. If the designer thought it would be better to save x in a file instead of a private member, your specification would still be met, but your test would fail.

So specifying the value of private items limits the developer of the class, and makes future changes more difficult.

Besides: officially you should define the specifications before you develop the class and the test, and thus you wouldn't know which private members are influenced by the function. The test should focus on whether the specifications are met, and not on how the functions are implemented.

Upvotes: 1

Jeffery Thomas
Jeffery Thomas

Reputation: 42588

@interface ImplementationClass () {
    TWPLoadUnitEnum loadUnit;
    BOOL recosMatchTargets;
}

Are not properties, they are iVars. If you want them to be properties, you need to have

@interface ImplementationClass ()
@property (nonatomic) TWPLoadUnitEnum loadUnit;
@property (nonatomic) BOOL recosMatchTargets;

If you want to keep the iVar, then in your unit test put

@interface ImplementationClass () {
@public
    TWPLoadUnitEnum loadUnit;
    BOOL recosMatchTargets;
}
@end
  • Note: this uses () instead of (Test). It looks odd but it will work.
  • Important Note: You must provide all iVars and they must be in the exact order or you may cause unexpected behaviors (like random crashes).

Upvotes: 1

Related Questions