Unit testing with NSURLConnection
-
03-07-2019 - |
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.
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.