Skip to content

objective-c test doubles on the cheap with brutal cast

Objective-C has the power of Ruby, with duck-typing and dynamic dispatch in the object layer. At the same time it has the power of C, with direct access to memory layouts and static-weak typing below the object layer. Sometimes, the two powers can be combined for some unexpected results.

On my current project we are trying to unit test as much functionality as we reasonably can. I am quite happy to write interactionist tests, so I need test doubles. Although the Objective-C compiler does static type checking at compile time, at run-time Objective-C objects will respond to any message for which they have a method defined.

This makes creating test doubles very easy. Consider a controller that accepts an error delegated from a CLLocationManager, and delegates it on to a logging class. Fragments of the classes involved might look like this:

@interface Logger : NSObject
 
- (void)log:(NSError *)error;
 
@end
 
@interface LocationSensitiveController <CLLocationManagerDelegate> : NSObject
 
- (id)initWithLogger:(Logger *)logger;
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
 
@end

In my test I would like to use a test double in place of the logger, and assert that the same error gets passed along:

- (void)testShouldPassErrorToLogger;
{
  Logger *stubLogger = // how to create the stub logger?
  LocationSensitiveController *controller = [[[LocationSensitiveController alloc] initWithLogger:stubLogger] autorelease];

The stub logger need only understand the log: message to serve its purpose. It does not need to have any relationship to the Logger class. I’ve been calling these classes “Pretend…” because the class is only pretending to be the other type. They would be stubs in the Test Double taxonomy that Martin Fowler popularised.

@interface PretendLogger : NSObject
 
- (void)log:(NSError *)error;
- (NSError *)receivedError;
 
@end

The compiler will reject a straight assignment:

  Logger *stubLogger = [[[PretendLogger alloc] init] autorelease]; // type error

The low-level C power can be used to convince the compiler otherwise:

  Logger *stubLogger = (Logger *)[[[PretendLogger alloc] init] autorelease];

In C this type of cast is sometimes called a brutal cast. The cast tells the compiler to interpret the same area of memory as a different type. All Objective-C classes share the same basic memory layout, so in the example the cast “sneaks” the PretendLogger past the compile-time static checking and in to the LocationSensitiveController instance. There it will receive messages intended for Logger, and because it implements a method for the same selector (log:), the code will run successfully.

Using a cast, I can write the test using my PretendLogger class:

@implementation LocationSensitiveControllerTest
 
- (void)testShouldPassErrorToLogger;
{
  Logger *stubLogger = [[[PretendLogger alloc] init] autorelease];
  LocationSensitiveController *controller = [[[LocationSensitiveController alloc] initWithLogger:stubLogger] autorelease];
  NSError *expectedError = [NSError errorWithDomain:@"domain string" code:666 userInfo:nil];
 
  [controller locationManager:nil didFailWithError:expectedError];
 
  NSError *actualError = [(PretendLogger *)stubLogger receivedError];
  GHAssertEquals(expectedError, actualError, @"error should be received by logger");
}
 
@end

Eventually a mocking framework makes sense, or real classes can be used with method swizzling. When getting started on a project or a new area of code, this is a very simple approach to get some interaction tests going.

I’ve posted a complete xcode project incorporating the example test to github.

Categories: brutal cast, casting, dispatch, dynamic, interactionist, message, objective-c, test doubles, testing, unit tests.

Comment Feed

4 Responses

  1. I noticed you injected the Logger dependency in the init method of the class under test. I think that definitely makes life a bit easier, regardless of whether you’re using OCMock or pretend test doubles.

    If you don’t inject the Logger class, it seems to get a bit harder to swizzle the pretend method into the class under test unless you use some sort of Objective C DIC (are there any good ones?)

    ie. if the method was more like:

    - (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error;
    {
      Logger *logger = [[[Logger alloc] init] autorelease];
      [logger log:error];
    }
    

    Method swizzling doesn’t buy you much because alloc/init are called together, and swizzling alloc sounds evil! Perhaps simple constructor-based dependency injection like you’re showing is the best way around this.

  2. While writing the blog I drafted a bunch of code in the post and then later stitched it together in XCode to make sure it all worked. I originally had a setLogger: method on the controller, but while implementing it seemed more natural to pass it to init. It works well enough that way.

    I hadn’t thought about alloc’ing it directly in the method. It doesn’t seem to make sense for a logger, but I know it has come up in other contexts.

    I still like dependency injection and don’t see a problem with it in obj-c. A container is not necessary after all.

    New motto: “Stop spreading the [alloc]s!”

  3. Personally, I try to avoid casts wherever possible. The C syntax for them is really hard to search for if you ever decide to change something. In this case you can use the Obj-C id type to avoid the need for the cast:

    id stubLogger = [[[PretendLogger alloc] init] autorelease];

    id can be used as a reference to any Obj-C type, and can be used wherever any Obj-C type is required.

  4. I like the “id” syntax. It is much cleaner than using a cast.

    Unfortunately, the code base I was using this on was configured so that any use of id was a compile time error. We had to use casts there. On other code bases I’ll use id.



Some HTML is OK

or, reply to this post via trackback.