huggie
huggie

Reputation: 18247

How to unit test some method that uses a class method

Say I have a method like this I want to test and it uses class methods:

@implementation User
...

- (void)methodToTest
{
    NSString *retval = [UserResourceTable staticMethod];
    // do something here
}

@end

Since UserResourceTable is not injected and it is a message to a class and not a instance, it doesn't look to me like it is amenable to mocking. I don't want the real method to execute, as it incurs network access.

Similar question is asked for C#. Jay says "This is one of the reasons that using statics in this way is sometimes frowned upon", though isn't that just the way with class methods and plenty of Apple's own libraries use it?

The advices given are to wrap it in an adapter so it can be messaged to an object instance instead and to inject it in the constructor.

Say I wrapped it like this:

@implementation UserResourceWrapper
- (NSString *)staticMethodWrapper
{
    return [UserResourceTable staticMethod];
}
@end

I am not so fond of the injection part. I think I do not want it to be exposed externally. So I still end up subclassing the User class and create a factory method to return, and have methodToTest calls it

@implementation User
- (NSString *)urt
{
    return [UserResourceTableWrapper new];
}

- (void)methodToTest
{
    NSString *retval = [[self urt] staticMethod];
    // do something here
}
@end

In unit test, I would subclass the class I want to test which is User and override that urt: method. That looks like a lot of work. Another possible method I can think of is to swizzle [UserResourceTable staticMethod]. This looks like a lot of trouble for the job. Risking for being marked as not in a good FAQ format blah blah so infamous on StackOverFlow (wink), what's the standard practice here? Is there a ready-made library to swizzle? It doesn't look like OCMock or OCMockito can do this, they appear to be able to be used only when injected.

EDIT:

I'm getting the feeling that injection is the best possible way. With subclassing + factory method, if I want to provide different behaviors for different test cases (success case & failure case, for example). I would need two mock classes each providing a different behavior. That's a lot of boiler plates for a few tests. With swizzle, once you swap it you swap it for good and it takes effect in all the tests. However if I can inject it, the mock can be created right within the unit-testing method and seems easy-peasy.

Upvotes: 0

Views: 810

Answers (1)

Jon Reid
Jon Reid

Reputation: 20980

What you want to do is inject UserResourceTableWrapper. Here's an example using setter injection using a property:

@property (nonatomic, strong) Class userResourceTableWrapperClass;

Then instead of directly calling

[UserResourceTableWrapper staticMethod]

you'd call

[self.userResourceTableWrapperClass staticMethod]

You can accomplish this using the various forms of dependency injection:

  • Property injection (shown above)
  • Constructor injection (that is, through initializer argument)
  • Method argument injection (if use has limited scope)

Extract and Override is another possibility, but I try to limit it to legacy code.

Upvotes: 1

Related Questions