Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to stub/mock C functions for iOS TDD?

I'm currently creating an application that is TDD but I'm having problems testing the usage of C functions that are required for my application. Here is an example function that that I need to test:

UIImageWriteToSavedPhotosAlbum(imageToBeSaved, nil, nil, nil)

How can I mock or stub a C method for TDD?

like image 308
5StringRyan Avatar asked May 26 '13 15:05

5StringRyan


2 Answers

There are several approaches to do it but is not as beautiful as in OOP languages. Here list which I use:

Using functions pointers

This approach is my favourite one because is the most flexible in my opinion.

In the header file (*.h)

int (*_fun) (int);

int fun (int a);

int fun_mock (int a);

In the Testing case file (*.C)

_fun = fun_mock;

In the normal case file (*.C)

_fun = fun;

Calling the function (main.C)

...
_fun ();
...

Compiling

If you want to make the TDD, you need to compile the testing file and the main (or the rest of the files) one. Otherwise do not compile the testing file.

Using macros to substitute the name of the function

In the header file (*.h)

If you want to call fun

#define FUN fun

int fun (int a);

int fun_mock (int a);

In the header file (*.h)

If you want to call the mock version

#define FUN fun_mock

int fun (int a);

int fun_mock (int a);

Calling the function (main.C)

...
FUN ();
...

Using this method we need to set the correct define before we compile any of the modules.

Using a struct with functions pointer

To be honest I never used this method but I read in some others places. The main idea is to have a struct pointers for all the different functions of the module, and whenever you want to change a function you just change the address of this function pointer on that struct. In the end is a similar strategy that the first method but implemented in a different way.

According to Timothy Jones

test-dept is a relatively recent C unit testing framework that allows you to do runtime stubbing of functions. I found it very easy to use - here's an example from their docs:

void test_stringify_cannot_malloc_returns_sane_result() {
 replace_function(&malloc, &always_failing_malloc);
 char *h = stringify('h');
 assert_string_equals("cannot_stringify", h);
}

Although the downloads section is a little out of date, it seems fairly actively developed - the author fixed an issue I had very promptly. You can get the latest version (which I've been using without issues) with:

svn checkout http://test-dept.googlecode.com/svn/trunk/ test-dept-read-only the version there was last updated in Oct 2011.

However, since the stubbing is achieved using assembler, it may need some effort to get it to support ARM.

The last point I copied from https://stackoverflow.com/a/9613821/2420872.

like image 73
Vicente Bolea Avatar answered Oct 17 '22 11:10

Vicente Bolea


I like to wrap the C function in an Objective C method so that it can be stubbed/mocked/expected with an Objective C mocking framework (e.g. OCMock).

SomeClass.m:

@implementation SomeClass
#pragma mark - Public
// This is the method we will test. It calls a C function internally, which we want to stub.
- (void)someMethod {
    UIImage *image = [self getImage];
    [self writeImageToSavedPhotosAlbum:image
                      completionTarget:nil
                    completionSelector:nil
                           contextInfo:NULL];
}

#pragma mark - Private
- (void)writeImageToSavedPhotosAlbum:(UIImage *)image
                    completionTarget:(id)completionTarget
                  completionSelector:(SEL)completionSelector
                         contextInfo:(void *)contextInfo
{
    UIImageWriteToSavedPhotosAlbum(image, completionTarget, completionSelector, contextInfo);
}
@end

SomeClassTests.m:

@interface SomeClass ()
- (void)writeImageToSavedPhotosAlbum:(UIImage *)image
                    completionTarget:(id)completionTarget
                  completionSelector:(SEL)completionSelector
                         contextInfo:(void *)contextInfo;
@end

@interface SomeClassTests : XCTestCase
@end

@implementation SomeClassTests
- (void)testSomeMethod {
    SomeClass *someInstance = [[SomeClass alloc] init];
    id someInstanceMock = [OCMockObject partialMockForObject:someInstance];
    [[someInstanceMock stub] writeImageToSavedPhotosAlbum:[OCMArg any]
                                         completionTarget:nil
                                       completionSelector:nil
                                              contextInfo:NULL];

    [someInstance someMethod];
}
@end
like image 23
tboyce12 Avatar answered Oct 17 '22 11:10

tboyce12