I can be whatever you want – Mocks and stubs

Probably when you read some articles about unit tests you can find a sentence like „Keep the unit small” or „For such reason there is a unit word before in „unit test” phrase ”

But… what does it mean? My UIViewController has 1000 lines of code! Is it possible to create a small test file for such a class? No, and, to be honest, I think that could be difficult to achieve :D.


3 types of verification

F(x) verification

What we want to achieve during our tests is to verify if for given input we get our desired output. It is simple to test a method which takes an argument and return something. We just call our function with arguments and check a response.

Return value verification

Return value verification

State verification

What if the method is a void type? In that case, we need to check how the state of the object has changed after a method call. That is also simple. We can compare a state of the object before and after calling the method by using proper accessors.

State verification

State verification

Behaviour verification

Very often in our code one object has a reference to another object. Object-oriented programming is all about sending messages between objects. What we want to test here is to check if our object has sent a message to another object.

Behaviour verification

Behaviour verification

Imagine you have a LoginViewController which has a dependent object LoginSystem. The job of LoginViewController is to check if credentials written by a user are correct and if the controller calls an API. How want we to test that? If LoginViewController is a test unit that means it needs another unit (LoginSystem) to work. Can we test them separately?

Yes, we can! Stubs and mocks help us!

Stubs and mocks imitate a behaviour of a class. Instead of using real LoginSystem object in our test we can use a stubbed or mocked version. We are able to do so thanks to Dependency Injection.

Mocks

Mocks are objects created by mocking library (I use OCMockito which I highly recommend 😉 ). Mocks give you a possibility to preset returned value of any method call without subclassing a dependency class. Mock also provides you easy way to check if a specific method was invoked on it.

    describe(@"loginButtonPressed:",^{
        context(@"when credential are valid",^{
            __block LoginRequest *expectedRequest;
            before(^{
                [given([loginSystem areCredentialValid:anything()]) willReturnBool:YES];

                NSString *expectedEmail = @"test@test.com";
                NSString *expectedPassword = @"password";
                subjectUnderTest.emailTextField.text = expectedEmail;
                subjectUnderTest.passwordTextField.text = expectedPassword;
                expectedRequest = [[LoginRequest alloc] initWithEmail:expectedEmail password:expectedPassword];
            });

            it(@"should login with loginSystem with credential provided by user",^{
                [subjectUnderTest loginButtonPressed:nil];
                [verify(loginSystem) loginWithRequest:expectedRequest];
            });
            
        });
    });

OCMockito gives us two functions to cooperate with mocks. given() and verify():

  • given() – is made for telling a mock what should it return for a specific method call. Moreover, you can also chain given() calls to achieve that mock will return „x” value in the first call and „y” in the second.
  • verify() – is made for checking if your SUT(system under test) has called the method with a specific argument.

Sometimes you want to check if SUT has invoked a method of a dependent object but you don’t care about what arguments were passed. You can always use anything() matcher. The same matcher can be used with given() as well. Matchers come from OCHamcrest. OCMockito is built on top of it.

Stubs

A stub is usually a subclass of our original object or a class which conforms the same protocol as the original object. Stub’s duty is to keep information about arguments which were passed to a method. It also gives you a possibility to decide what stubbed method call should return.

        describe(@"loginButtonPressed:", ^{
            context(@"when credential are valid", ^{
                __block LoginRequest *expectedRequest;
                before(^{
                    loginSystemStub.stubedAreCredentialValid = YES;

                    NSString *expectedEmail = @"test@test.com";
                    NSString *expectedPassword = @"password";
                    subjectUnderTest.emailTextField.text = expectedEmail;
                    subjectUnderTest.passwordTextField.text = expectedPassword;
                    expectedRequest = [[LoginRequest alloc] initWithEmail:expectedEmail password:expectedPassword];
                });

                it(@"should login with loginSystem with credential provided by user", ^{
                    [subjectUnderTest loginButtonPressed:nil];
                    expect(loginSystemStub.usedRequest).to.equal(expectedRequest);
                });

                //next test case like load wait indicator and so on ...
            });
        });

When stubs, when mocks?

In the beginning, I recommend you to use mocks. They are easier to write, you don’t need to create any new file. Usage of given() and verify() is readable. When you are using mocks you are able to take benefits from matchers like anything(). It seems that mocks can handle everything… and here is a trap.

Sometimes mocks are not enough.

I cheated in mock’s sample. In that example loginWithRequest: method takes only one argument – a request. Usually, such method should have at least one argument more – a callback. The behavior invoked inside the callback is also what you need to test. I had big problems to invoke callback using mocks. It’s much easier to use stubs here.

willDo:

In a meanwhile when I was writing this article Jon Reid has released a new version of OCMockito. The release has provided a new givenVoid() function. Now it is much easier to invoke callbacks block.

[givenVoid([apiConnection invokeRESTRequest:loginRequest 
    success:anything() failure:anything()]) willDo:^id(NSInvocation *invocation) {
            APIConnectionSuccessCallback successCallback = [invocation mkt_arguments][1];
            successCallback(@{@"success" : @"ok"});
            return nil;
        }];
   }];

Can that replace stubs? Yes, and I think it will ;). However, remember that sometimes it will be difficult to express something with mocks. Don’t be afraid to create a stub then.

Mocks love objects – Problem with NSError**

In Objective-C world, we have a special way (in my opinion an ugly way :D) to report that something has gone wrong. We pass a pointer to NSError (NSError**) to a method as an argument. It is problematic to use given() and verify() with NSError** because it is not an object and OCMockito doesn’t have a default matcher for that. However, there is possibility to force given() and verify() to work with NSError**.

//In UserCoreDataWriter.h file
@interface UserCoreDataWriter : NSObject
...
- (BOOL)saveUser:(UserEntity*)user error:(NSError**)errorPointer; 
...
@end

-------------------------------------------
//In test file where we use UserCoreDataWriter as dependant object. For example in LoginSystemTests.m
...
[given([userWriterMock saveUser:user error:NULL]) withMatcher:anything() forArgument:1] willReturnBool:YES];
...
[[verify(userWriterMock) withMatcher:anything() forArgument:1] saveUser:user error:NULL];
...

The above example allows us to use mocks when we don’t care about NSError** argument. However, we should care! Usually, we also want to test what will happen if an error value will be attached to NSError** pointer. I don’t have simply solutions to solve this problem… Or maybe I have… Use stubs here 😉

Last word

The moment when I understood that writing unit tests can be simple was the moment when I got to know mocks. I think that „keep the unit small” and similar phrases have some sense now.

Of course, that was only an introduction. If you would like to go further with mocks and stubs read Martin’s Fowler article „Mocks Aren’t Stubs”. All the code what I used here is available on my Github.

We have discussed all 2 (and a half. Don’t forget about Expecta ;)) tools which allow you to write clean and readable tests. Now I would like to ask you a question:

What was the biggest problem for you when you started writing tests on iOS?.

Share your answer in comments or on Twitter.

Don’t forget to share the article 😉

You Might Also Like