[[MyObject alloc] initForTesting]?

Normally I don’t believe in adding additional code just to support unit testing. By this I mean adding #defines and ifs and switches and such to product code, to change program flow during testing. The reason I don’t like doing that is because you can create a situation whereby the product code path could be different than the code path that is tested.

However, I think I can make a good argument for splitting code up in ways to make unit testing easier. One situation that I keep coming across has to do with a class init method. On one hand, I’d like init to prepare the class for use, including setting reasonable default values, setting up observers, and so forth. But this could cause problems for unit testing.

I have discussed previously having a method call other helper methods to do the actual work, allowing those helper methods to be tested more directly. I think that is a great pattern, and it applies equally well to init. But this poses a bit of a problem for unit testing. For example:

// In the object code being tested
- (id)initAndStart {
   self = [super init];
   if (self) {
      [self setPostalCode:@"Unknown"];
      [self startLocationUpdates];
   }
   return self;
}

In order to test an object, we need to create and save a reference to a test copy. Typically this is done in our test case file’s setUp method. In this case we’re saving to a location property (not shown).

- (void)setUp {
    [self setLocation:[[Location alloc]initAndStart]];
}

One way to unit test this would be to try to verify that the test object initialized everything correctly. For for example:

- (void)testThatInitSetsPostalCodeToUnknown {
    STAssertTrue([self.location.postalCode isEqualToString:@"Unknown"], @"Default postal code should be Unknown but is %s",self.location.postalCode);
}

This might work. But what if subsequent lines of code in the init method cause the postalCode property to change. In this example, starting location updates is expected to periodically update the postalCode property. If that happens before our test completes, then the test will fail.
So in this case, it might be better to take another approach, and split the initialization of this object in half as shown here:

// In the object code being tested
- (id)initForTesting {
   return [super init];
}
- (id)initAndStart {
   self = [self initForTesting];
   if(self) {
      [self setPostalCode:@"Unknown"];
      [self startLocationUpdates];
   }
   return self;
}

Now we can change our test case file’s setUp method to initialize the object using initForTesting, and perform specific tests on the initAndStart.

Hopefully, you are asking yourself at this point “What is the difference between a standard init and initForTesting? And the answer is “None, in this case”. I’ve only named it this way to point out it’s significance to unit testing.

So how do we test initAndStart? We use a partial mock to intercept the call to initForTesting. If we don’t do this, then our mock object will be replaced with a new instance in the call to initForTesting.

- (void)testThatInitAndStartCallsStartLocationUpdates {
    id mock = [OCMockObject partialMockForObject:self.location];
    __unused id tempmock = [[[mock stub]andReturn:mock]initForTesting];
    [[mock expect]startLocationUpdates];
    __unused id initedMock = [mock initAndStart];
    [mock verify];
}

Note: in the above code, __unused is only there to eliminate compiler warnings.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.