The Swifty Little Mocker

In my last post, I wanted to focus on showing you how Clean Swift works. So I didn’t write any test. You saw how Clean Swift can help you organize your codebase, help you write clean code, and find things later. You learned how to extract your business logic into the interactor and presentation logic into the presenter in the VIP cycle. Finally, you saw how Swift protocols help you decouple your objects.

This post focuses on these protocols that result in a side (or rather main) benefit: Much easier unit testing. Without tests, we can’t be confident the changes we make do not affect existing code.

But first thing first.

Based on Uncle Bob’s The Little Mocker, the following is a short introduction to the different kinds of test objects – in Swift. I highly recommend you read his funny yet educational post.

Java interface == Swift protocol

Nuff said.

Test double

The name test double refers to the whole family of objects that are used in tests.

Dummy

You pass a dummy as an argument to a function when you don’t care how it’s used. As part of a test, when you must pass an argument, but you know the argument will never be used in the method being tested, you pass a dummy. Since a dummy is never used, it can return anything or nothing. Returning nil is perfectly acceptable and most logical.

DummyAuthorizer is a test dummy and returns nil because we don’t really care.

An example test

We want to test a newly created system has no logged-in users. System is a class whose initializer must take an Authorizer as argument to determine permissions. But since our goal is to make sure a new user should have a zero login count, we don’t care about permissions or the Authorizer that we must pass in. The DummyAuthorizer is perfect for this case because it is so dumb that it doesn’t even know what username and password mean.

Stub

A stub is a dummy that returns a specific value because the rest of the system relying on this specific value to continue running in a test.

If the rest of the System class relies on the authorize() method returning true in order to continue, we can make AcceptingAuthorizerStub to always return true. We don’t care what the username and password are, we are just going to log anyone in. Our goal is to test the login count after a successful login. We’ll define what a successful login means in another test.

Spy

You use a spy when you want to make sure a method is called in your test. It can spy on more stuff, such as how many times a method is called and what arguments are passed in. But you have to be careful. The more stuff you spy, the tighter you couple your tests to the implementation of your app. Coupling leads to fragile tests.

The AcceptingAuthorizerSpy keeps an authorizeWasCalled boolean. When the authorize() method is called, it records that fact. In your test, during your assertion, you can verify the invocation indeed happened.

Mock

A mock is similar to a spy and does a little more. It also tests behavior by having the assertion go into the mock itself. A mock is not so interested in the return values of functions. It’s more interested in what function were called, with what arguments, when, and how often.

The AcceptingAuthorizerVerificationMock’s verify() method returns true only if the method invocation happens. Instead of peeking inside a spy in your assertion, you call the mock’s verify() method to assert it is true. A spy spies on citizens because they won’t behave without authority, whereas a mock is self policing.

Fake

So far, all the test objects you’ve seen don’t care about what arguments you pass in. Even a mock only records the arguments, it doesn’t use the arguments to generate a different return value. However, a fake has specific business logic. You can drive its behavior by passing in different arguments to make it return different results.

When you are testing a certain business rule (for example, a user can only log in with correct username), you use AcceptingAuthorizerFake. The authorize() method returns true only if the username is ‘Bob.’

The whole enchilada

The following is a fully functional test written in Swift using Xcode’s default XCTest. You can copy and paste into a new file in Xcode and it’ll pass.

In the testNewlyCreatedSystem_hasNoLoggedInUsers() method, you can swap in any of the test objects and the test still passes. The key takeaway is this. As the System class grows and the Authorizer being used differently in its implementation, you also need to make your test double more sophisticated to make sure the test continue to pass. This means your original DummyAuthorizer may need to evolve into a stub, spy, mock, or fake.

That’s it for the Swifty Little Mocker.

What can you do with the Swifty Little Mocker?

In a future post, I’ll show you the entire TDD approach from start to finish. That is, given the requirements, how can you write a test first to drive the feature implementation.

Better yet, you’ll use what you’ve learned in this post to write your own mocks and stubs. You won’t need or want to learn a new testing or mocking framework because you’ll see writing your own is so much easier. No more worries about a CocoaPod breaking your build or keeping up to date with extra frameworks.

Best yet, your tests will be blazingly fast because there is no external frameworks to import and load. You only need to test your boundary methods, not private methods. You’ll see why and how.

If this sounds interesting to you, make sure you subscribe below so you don’t miss out. As a bonus, you’ll also get my Xcode templates so you can start using the Clean Swift architecture in your iOS projects today.

In the next post, you’ll learn what unit testing really mean? Do you really need to test every single method of every class? How do you make sure every code path is covered? How do you avoid writing fragile tests?

Stay tuned.

Get the Clean Swift Xcode Templates

Subscribe below to get my Xcode templates and learn how to apply the VIP cycle to your projects, extract business and presentation logic into interactor and presenter, navigate to different scenes using multiple storyboards, and write fast, maintainable tests with confidence to make changes.

I promise I'll never send you spam. You can unsubscribe at any time.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

7 Comments

  1. Getting the following compile error on XCTAssertTrue

    After I added the following test case to test the “Spy” scenario

Leave a Comment

Your email address will not be published. Required fields are marked *