2

I am writing a unit test for a class Foo, which has a collaborator, Bar. I want to use a manually-built stub implementation of Bar in Foo's test.

If I were doing this in Java, I would give Foo a BarFactory collaborator and inject a MockBarFactory in Foo's test that always returned my StubBar.

I know this technique will work fine in Objective-C but it doesn't strike me as a particularly idiomatic thing to do in a dynamic language. I am wondering if I can do anything tricky that will cause [[Bar alloc] init] to return StubBar while I'm running my unit test but give me the normal implementation of Bar in "real life".

Or is the obvious factory pattern the most appropriate thing to use in this case?

Robert Atkins
  • 23,528
  • 15
  • 68
  • 97
  • I would not mixup code for unit-testing with "real life" code, how can you be sure your tests are fine if they behave differently? – Jonathan Cichon May 16 '13 at 10:03
  • @JonathanCichon I think that's a common thing to do when unit testing, isn't it? When you want to test some specific object or function (like `Foo` in this case) you can't always setup a complete testing environment for different reasons. Maybe `Foo` has to have a `Bar` property that's not actually relevant to the tests. – DrummerB May 16 '13 at 10:06
  • As I comment below, I would like to avoid adding stuff that compiles into my main target if it's solely used for unit testing. – Robert Atkins May 16 '13 at 10:13
  • @DrummerB In this case I need `Bar` to behave predictably so I can test all the code paths through `Foo` that are dependent on how `Bar` behaves. – Robert Atkins May 16 '13 at 10:15

2 Answers2

1

You can return a different object in init. That's why you're supposed to assign the return value of [super init] to self. Try something like this:

@implementation Bar 

- (id)init {
    if (UNIT_TEST) {
        self = [[StubBar alloc] init];
        if (self) {
            // do unit test init here
        }
    } else {
        self = [super init];
        if (self) {
            // do regular init here
        }
    }
    return self;
}

...

@end

Note: This should work under ARC. If you're not using ARC, make sure you release self, before assigning the new StubBar instance.


If you want to avoid compiling unit test related code into your main target:
@implementation Bar 

- (id)init {
#if UNIT_TEST
    self = [[StubBar alloc] init];
    if (self) {
        // do unit test init here
    }
#else
    self = [super init];
    if (self) {
        // do regular init here
    }
#endif
    return self;
}

...

@end

If you want to completely separate unit testing and real code, you could just have two different versions of your Bar class. One will be compiled with the real code, the other one with the unit test target.


You can easily allocate an instance, without knowing the exact class type at compile time like this:

id someBar = [[someClass alloc] init]; // assuming someClass is of type Class

or:

id someBar = [[NSClassFromString(@"Bar") alloc] init];

The first one is preferable though. You could use this to have a default class type that you can change when you're unit testing. Either as a property of Foo itself, or a preprocessor macro that you redefine when unit testing.

DrummerB
  • 39,814
  • 12
  • 105
  • 142
  • While I can see how this would work, I don't like the idea of mixing unit testing stuff into my real codebase. – Robert Atkins May 16 '13 at 10:13
  • @RobertAtkins Added a couple more suggestions. There is really a ton of ways to do this, I don't think there is THE way, and as you mentioned factories would work just as well. – DrummerB May 16 '13 at 10:26
  • I don't understand how I'd implement your last suggestion in my case where `Bar` is constructed by the class-under-test _inside_ its implementation. The problem a Factory solves for you in Java is that `new` is a static call and can't be mocked out at runtime. The gist of my question is, "is there something clever I can do in Objective-C to get around the fact that `[Bar alloc]` is a static call?" – Robert Atkins May 16 '13 at 11:18
  • Also, the way we have our unit tests set up, the unit test target depends on the main target so defining the class twice won't work I don't think. – Robert Atkins May 16 '13 at 11:19
  • How would you assign the factory to `Foo` in Java? Is it a static variable? An ivar? – DrummerB May 16 '13 at 11:29
  • I don't think alloc is the static call you think it is. Remember, classes are objects too, so you can also do [Foo performSelector:@selector(alloc)]. I don't recommend this, but it's possible. – casademora May 16 '13 at 13:30
  • @DrummerB In Java you'd be using some kind of IoC container (like Spring) and have the implementation of `BarFactory` injected at runtime for live code, but you'd manually pass the `StubBar`-returning version in (either constructor injection or setter injection) in the unit test setup. – Robert Atkins May 17 '13 at 08:26
  • @casademora That's the kind of trick I was wondering if a) existed and b) was regularly used to accomplish my original goal. – Robert Atkins May 17 '13 at 08:28
  • You can replace the implementation of a method at runtime, if that's what you're looking for. It's called [Method Swizzling](http://cocoadev.com/wiki/MethodSwizzling). However I don't think this is a technique that is often used (for unit testing). You have to be very [careful](http://stackoverflow.com/questions/5339276/what-are-the-dangers-of-method-swizzling-in-objective-c). – DrummerB May 17 '13 at 11:16
0

What you are looking for is something along the lines of OCMock. It allows you to build mock objects from classes, protocols and it especially made for unit testing.

aLevelOfIndirection
  • 3,522
  • 14
  • 18