Question

I want to test a piece of code that uses network (the NSURLConnection class, to be specific). The code (let’s call it NetworkManager) looks a bit like this:

- (id) buildConnection
{
    // some more code and then:
    return [NSURLConnection …];
}

- (void) startNetworkSync
{
    id connection = [self buildConnection];
    //…
}

In the unit test I would like to get rid of the networking, ie. replace the NSURLConnection object by a mock. How do I do this?

I’ve tried creating a partial mock of the NetworkManager that would replace the buildConnection method by a stub. The problem is that partial mocks as done by OCMock only stub messages from the outside world – sending buildConnection from the startNetworkSync invokes the original method, not the stub.

I have also tried monkey-patching the NetworkManager class through a category. This works, I can easily override the buildConnection method by other code and replace the real NSURLConnection with a stub. The problem is that I found no simple way I could get the stubbed connection in the test – the connection is a private part of the NetworkManager.

Then I could subclass the NetworkManager, override the buildConnection method and add an instance variable plus an accessor for the created connection. This seems like a lot of code, though.

How would you solve this? I am looking for a solution that keeps the NetworkManager class design clean and does not require much magic nor much code in the test.

Was it helpful?

Solution

This is the kind of thing dependency injection is designed to solve; if you use startNetworkSyncWithConnection:(NSURLConnection*) instead you can easily test the method with a mock connection. If you don't want to change the API for your clients you could even keep startNetworkSync as a wrapper that does nothing but call that new method with [self buildConnection] as the argument.

OTHER TIPS

I modified OCMock to support real partial mocks, see the repo on GitHub.

Another solution I have used recently is to completely abstract the networking interface. If the class needs some data from the network, it probably interacts with some server service that can be explictly modelled as a protocol:

@protocol SomeNetworkService
- (NSArray*) allAvailableFoos;
- (void) insertNewFoo: (Foo*) foo;
@end

And then you’ll have a real HTTP implementation and a testing one. This means more work, but also much better testability. The tests are less brittle and much more convenient, since the testing network layer can do whatever you need.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top