views:

132

answers:

3

I'm wondering how to go about testing this. I have a method that takes a parameter, and based on some properties of that parameter it creates another object and operates on it. The code looks something like this:

- (void) navigate:(NavContext *)context {
  Destination * dest = [[Destination alloc] initWithContext:context];
  if (context.isValid) {
    [dest doSomething];
  } else {
    // something else
  }
  [dest release];
}

What i want to verify is that if context.isValid is true, that doSomething is called on dest, but i don't know how to test that (or if that's even possible) using OCMock or any other traditional testing methods since that object is created entirely within the scope of the method. Am i going about this the wrong way?

A: 

It's completely possible, using such interesting techniques as method swizzling, but it's probably going about it the wrong way. If there's absolutely no way to observe the effects of invoking doSomething from a unit test, isn't the fact that it invokes doSomething an implementation detail?

(If you were to do this test, one way to accomplish your aims would be replacing the doSomething method of Destination with one that notifies your unit test and then passes on the call to doSomething.)

John Calsbeek
I suppose it's not a big deal if i don't test that the method is called. I'm still somewhat new to TDD/unit testing so i don't know all of the best practices yet :)
Kevlar
+3  A: 

You could use OCMock, but you'd have to modify the code to either take a Destination object or to use a singleton object which you could replace with your mock object first.

The cleanest way to do this would probably be to implement a

-(void) navigate:(NavContext *)context destination:(Destination *)dest;

method. Change the implementation of -(void) navigate:(NavContext *)context to the following:

- (void) navigate:(NavContext *)context {
    Destination * dest = [[Destination alloc] initWithContext:context];
    [self navigate:context destination:dest];
    [dest release];
}

This will allow your tests to call the method with an extra parameter directly. (In other languages, you would implement this simply by providing a default value for the destination parameter, but Objective-C does not support default parameters.)

BJ Homer
This seems to be the easiest answer, but it seems a little messy to have the destination object live in multiple scopes when it isn't really needed elsewhere, just to support easy testing. I guess compromises need to be made somewhere :)
Kevlar
Yep; if you want to be able to substitute a separate Destination object, then you have to have some way of passing it in. This is the cleanest way I know of to do that.
BJ Homer
+1  A: 

What i want to verify is that if context.isValid is true, that doSomething is called on dest

I think you may be testing the wrong thing here. You can safely assume (I hope) that boolean statements work correctly in ObjC. Wouldn't you want to test the Context object instead? If context.isValid then you're guaranteed that the [dest doSomething] branch gets executed.

EightyEight
I'm mocking the context object since in this test i don't care how it gets created, i care that the method does the right thing based on the context's properties.
Kevlar
Further, if the implementation of navigate: ever changes, he may still want to verify that doSomething is called.
BJ Homer
Ahh fair enough, I was being a little short sighted.I like BJ Homer's approach then.
EightyEight