Test Doubles in Swift
Introduction
Test Double is a generic term for any case where you replace a production object for testing purposes.
Source: https://martinfowler.com/bliki/TestDouble
Unfortunately, Swift doesn’t have a robust and stable framework for mocks, like Mockito, jMock and others. Of course, there are some libraries out there, but they are very limited and require lots of boilerplate code. Therefore, we tend to write our doubles manually.
In this post, I won't cover anything about how to write testable code, but I'’ll leave you with a video that was a game changer for me, when I was starting to test my code.
Types of Doubles
There are 5 types of doubles:
- Dummy: it's just a placeholder, that doesn't affect your test in any manner.
- Fake: simplified implementations of your actual component, making them not suitable for production.
- Stub: provide pre-configured outputs for the method calls made during the tests. Usually responding simply with what is necessary for the test.
- Spy: a type of stub that also record some information about how the actual component was used.
- Mock: a pre-programed object with the validations and expectations about how it will be consumed.
Dummies
A Dummy is basically a test double that doesn't do anything.
You might use it as a placeholder for something that you need in order to setup your tests, but won't have any effect on them.
Example:
Fakes
A Fake is a double that aims to decrease the complexity of the test, simplifying the implementation, and mimicking the real behavior.
For example, when creating an integration test for something that depends on a local database, you can make a fake that simplifies it and store the data in memory.
Example:
If we are testing something that uses the UserService, we shouldn't use the real validations, since it's could be slow and require other setups that would make our test more complex.
In this case, we could create a fake for UserServiceProtocol, like the example below:
Stubs
Stub is a kind of test double that you use to control the outcome of the said dependency.
Example:
Considering that you want to test if the numberOfPosts changed, you can do something like this:
Notes:
- Consider defining a pattern when naming the stub properties.
I like to use method name + ToBeReturned, as shown in the example above. - Avoid setting up the stubbed values on the stub's init method. This way, you can change it after initialization, and avoid duplication on your test code.
Spies
Spy is a test double that you can use to inspect the properties of some dependency used by the SUT (System Under Test).
Example:
To verify that the last user logged in was properly saved, we can use a Spy like shown below:
Spy Property conventions:
- if it is a method:
Name + Called
- if it is a parameter:
Name + Passed
- if it is a computed property:
Name + Accessed
- if it is a method and # of calls is relevant:
Name + Count
- all of them should be
private(set)
- a good location to put them is just above the corresponding function.
Note: You can create a Spy that is also a Stub, and combine their power to write your tests.
Mocks
Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don’t expect and are checked during verification to ensure they got all the calls they were expecting.
Source: https://martinfowler.com/bliki/TestDouble.html
Mocks can be one of the double types mentioned above, like a Spy or Stub, that handles some of the validations and asserts by themselves.
I personally don't use them regularly, and won't talk about them now, but you can learn more about them on the suggested links below:
- https://www.martinfowler.com/articles/mocksArentStubs.html
- https://academy.realm.io/posts/making-mock-objects-more-useful-try-swift-2017/
Conclusion
The more test you write, the better you will get. Knowing the proper name of the tools, will help you to create a clear and concise test suit.