I decided to add unit tests to my project and continue development in a test-driven kind of way. I’m currently working on implementing unit tests for my ManageSieve client object and I’m not sure what’s the best way to test that beast.
My SieveClient
object relies on two other objects for the network communication: CocoaAsyncSocket
and my own SaslConn
object, which is my wrapper around the Cyrus SASL library to handle the authentication methods. For testing I need to replace those with mock objects. I’m going to use the OCMock framework for this. I’m not quite sure how to do this, since the SieveClient object needs to create those objects itself. Right now I overwrite the (private) setters for that object to always install my mock objects using OCMocks partialMockForObject:
method. But this feels not right to me. Any ideas how this could be solved better?
The other part I have trouble with is the socket itself. To be able to test the protocol details I’d need a way to return predefined test data from the socket. I suppose I could just use OCMock mechanisms to fake the return values from the socket. But since CocoaAsyncSocket
provides many different methods to read data from the socket I have to know exactly which are being used by the protocol object in which order. I don’t want my unit test to be that dependent on implementation details of my protocol object. So what should I do here? Implement a mock object for the socket class by hand? This seems non-trivial, so I’d probably need unit tests for that too. Is that a good idea?
I’ve read that if something is hard to test it’s probably not very well designed either. But I don’t see how I could do better, since the hard part lies in interacting with the socket which I have to do.
If you’d like to see code you can find it at Bitbucket: SieveClient.m and SieveClient.h
So I read about Dependency Injection, and I think I’m going to use this to get the AsyncSocket
and SaslConn
objects into my SieveClient
object. I’ll change my constructor to accept those objects and use them. Since the user of this class usually doesn’t care about the socket and the SASL object I’ll add a factory method (in the form of a convenience constructor) that just creates those objects and passes them to the constructor.
But this solves only the first (and easier) part of my testing problem.
But then I rejected the idea, because it wouldn’t help very much. I could test the SieveClient object more easily, true. But then I’d have the same problems with testing the new object. Seems to me that this is just putting up the trouble for later. Especially since I have nothing I could re-use the new class for.
It wouldn't be the same problem.
I'll assume you need SieveClient to control the instantiation of the other objects internally because its part of an API you don't want to expose. If that's the reason, by separating them, you no longer have the same need, since you can have the SieveClient control the bindings and the other part that does the protocol receives the instances it'll work with.
By doing the above, you can hand over the mocked objects to your protocol implementation. Those mocks would then have any expectations you may need. If you find it you end up with it being too involved, then you probably need to refocus responsibilities, which usually results in a cleaner/simpler protocol implementation anyway (if you find that you need to from doing those unit tests).
Above said, you also need to consider if the code you are trying to test is as focused as possible on the protocol and doesn't have any extra elements. If that's the case, it wouldn't be a good candidate to unit test it, as its sole responsibility is the interaction with an external system. I'd decide how important is the protocol spec in this system, and if its all about integration with an external system I'd treat it like a focused integration test instead that hits the real external system and is kept separated from the unit tests (so it doesn't affects the speed needed to run unit tests of the rest of the system).
After re-reading the question because of the edit, I have to stress out what I said about focused integration tests above. You ask:
But since CocoaAsyncSocket provides many different methods to read data from the socket I have to know exactly which are being used by the protocol object in which order. I don’t want my unit test to be that dependent on implementation details of my protocol object. So what should I do here? Implement a mock object for the socket class by hand? This seems non-trivial, so I’d probably need unit tests for that too. Is that a good idea?
If you are dealing with a very complex object and that object is all about integration beyond a boundary, you usually are best avoiding it as part of unit tests. In that scenario you want a focused integration test / to hit the real external system. This doesn't mean all the unit tests of the rest of your code, hit the external system, just the very simple unit of code/class that uses that object.
It may very well be the case that such object is SieveClient in your scenario, in that case forget about unit tests of that piece of the code. What you want to do instead is mock the SieveClient when testing code that uses it. On the other hand, if you find that SieveClient is much more than that, you want to add a class that simplifies these communication aspects, and that'd be what you mock when testing SieveClient and also what you do a focused integration test against.
This type of tests are a very effective way to make sure that the code that interacts with the external is working as expected, since that's the focus of both the class and the tests involved. In case something on the external system starts working differently, you notice it clearly - as opposed to having it mixed with your application logic or worst not tested at all.
Can you split what you're doing into two parts, one of which is the abstract protocol and the other of which is the binding to sockets? Then you can test the abstract protocol more easily, and focus the testing of the binding on whether it invokes methods/operations of any connected abstract protocol correctly.
Abstractly, you'd be decreasing the coupling between the parts of your code. That increases testability, at a cost of some increase in overall complexity (though not too bad because you're getting better tools to manage it through Separation of Concerns) and some potential decrease in performance (not much of an issue with most systems though; your computer is far faster than its I/O subsystems).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With