Verifying method calls in Objective-C with primitive array/pointer arguments

406 Views Asked by At

I have two methods in a class I'm testing:

- (NSUInteger)sendBuffer:(uint8_t *)buffer length:(NSUInteger)length;
- (BOOL)sendFormattedCommandForAddress:(uint8_t)address 
                              withData:(uint8_t)data 
                        andCommandType:(ZKZSensorCommandType)commandType;

-sendFormattedCommandForAddress:withData:andCommandType: builds a char array and passes the pointer to this array to -sendBuffer:length:. The contents of this array vary depending on address, data, and commandType. In my testing, I want to verify that the correct array is built and passed to -sendBuffer:length:. OCMock won't let me set an expectation on the uint8_t * argument. OCMockito/OCHamcrest won't let me use a partial mock (yet). I've tried swizzling the -sendBuffer:length: method with one that calls a method in my test case class, and set an expectation on that method call. However, when the swizzled method is invoked, self points to the class under test instead of my test case. I could persist the buffer in my class under test and then check the contents of this buffer in my test, but I hate adding something to production code only to support testing. Does anyone have a better suggestion for how to test this behavior?

2

There are 2 best solutions below

0
On

While this is arguably a matter of opinion, the benefits of comprehensive unit testing make a small and contained increment in code complexity a worthwhile trade-off. In the case you describe, I think your idea of adding to the production class a buffer so that the last byte sequence sent can be retrieved and asserted against in your unit tests is a perfectly fine approach.

I personally might add a boolean property that controls wether the buffer is maintained or not, just to avoid any accidental unnecessary memory overhead if large messages end up being sent in the production code. The unit test setup would of course set this property, while the application / framework production code would not. Again, the key is to make the added code so simple that it is obviously correct. :-)

If you desire or need to be rigorously strict about the interface your production classes expose, you could also consider making the buffer-maintianing code dependent on conditional compilation. I personally prefer the former approach, but to each her own.

0
On

You don't need a mocking framework to make mocks. The challenge, I take it, is that you don't actually want to call -sendBuffer:length:. In that case, simply use Subclass and Override Method, described in Michael Feathers' book Working Effectively with Legacy Code.

Not knowing the name of your class, I'll call it Sender. This is what I would write, putting it straight into the SenderTest.m:

@interface TestingSender : Sender
@property (assign, nonatomic) NSUInteger sendBufferCount;
@property (assign, nonatomic) uint8_t *sendBufferBuffer;
@property (assign, nonatomic) NSUInteger sendBufferLength;
@end

@implementation TestingSender

- (NSUInteger)sendBuffer:(uint8_t *)buffer length:(NSUInteger)length
{
    ++_sendBufferCount;
    _sendBufferBuffer = buffer;
    _sendBufferLength = length;
}

@end

The test code then creates a TestingSender instead of a Sender.