views:

57

answers:

4

My problem is I am getting an error:

OCMckObject[NSNumberFormatter]: expected method was not invoked:setAllowsFloats:YES

I have written following Code:

(void) testReturnStringFromNumber
{
    id mockFormatter = [OCMockObject mockForClass:[NSNumberFormatter class]];
    StringNumber *testObject = [[StringNumber alloc] init];   

    [[mockFormatter expect] setAllowsFloats:YES];
    [testObject returnStringFromNumber:80.23456];
    [mockFormatter verify];
}


@implementation StringNumber

- (NSString *) returnStringFromNumber:(float)num
{
    NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
    [formatter setAllowsFloats:YES];

    NSString *str= [formatter stringFromNumber:[NSNumber numberWithFloat:num]];

    [formatter release];
    return str;
}

@end

+3  A: 

Because your StringNumber implementation uses its own NSNumberFormatter object, not the one you created in your test case. You need to perform a classic refactoring operation called "dependency inversion", where your StringNumber object takes its formatter as a parameter or ivar instead of creating it internally. Then you can pass your mock formatter to it as part of your test.

Graham Lee
Thanks Graham. i did that and it is working now. But, I want 2 know is there any way to test the internal objects inside a function?And, please tell me, is there any good tutorial with examples on OCMock with iPhone is available on the internet? I searched a lot, but found very less , almost none. Please help. Thanks
san
@san: You can't do that. You can test the overall behaviour of the function/method. If you need finer grains, then your API is too coarse and you need to break up your methods, or parameterise the objects they work with.
Graham Lee
A: 

Hi,

You can try to dynamically override init method of NSNumberFormatter in order to return your mock object. In your tests you have to make an ivar from id mockFormatter. So your test method becomes:

(void) testReturnStringFromNumber
{
    mockFormatter = [OCMockObject mockForClass:[NSNumberFormatter class]];
    StringNumber *testObject = [[StringNumber alloc] init];   

    [[mockFormatter expect] setAllowsFloats:YES];
    [testObject returnStringFromNumber:80.23456];
    [mockFormatter verify];
}

Then you have to add a category for NSNumberFormatter class

@implementation NSNumberFormatter (mock)

- (id)init {
   id object;

   if (mockFormatter) {
      object = mockFormatter
   } else {
      object = invokeSupersequent();

   return object;
}

@end

So when init method of NSNumberFormatter will be called in your code under test, a mock object will be used if you have set it.

Sources: - http://cocoawithlove.com/2009/12/sample-mac-application-with-complete.html

Regards, Quentin

Quentin
A: 

Hi (again),

Another idea: You can try to make an ivar with formatter in StringNumber class. Then, it will be possible to set formatter value using Key Value Coding from your tests.

Regards, Quentin

Quentin
A: 

For the code sample you present, I wouldn't worry about mocking NSNumberFormatter. You're trying to verify that [StringNumber returnStringFromNumber:] returns an appropriate string. Your test shouldn't care in this case what method it uses to calculate that string, just that your method works as intended.

Since you just return the value returned by numberWithFloat:, and NSNumberFormatter is a utility class, you shouldn't mock it. Then if the behavior of numberWithFloat: changes in the future, your test will fail and you can decide whether to change the test or change your implementation.

If you mock NSNumberFormatter, you're basically just testing that your method will return whatever it returns, so if its behavior changes, the behavior of your class will change too, an you won't know it (at test time).

I tend to use mocks to:

  • factor out a dependency that is dependent on a particular environment/configuration/data set
  • factor out a dependency that won't work at test time
  • verify the behavior of my class for different cases of the dependent code's behavior (e.g., when dependency returns nil, when dependency returns negative value, when dependency throws exception, etc)
chrispix