A great process to know how to unit test (your Swift code)

Daniel Souza
Jul 25, 2017 · 6 min read

I will show you a way to identify what you should test and some concepts to make your life a little easier when writing tests.

At some point in my career I knew how important unit tests were, but I didn’t know how to test my code. Many have faced the same issue. We read articles on how to start, but still, it’s hard to apply what we’ve learnt to our own code. We usually don’t have those functions that just sums up two parameters and returns the result, and we see it in many examples out there. Well, I came across a process that made it really easy for me.

The first thing I want to say is that you don’t need to change your architecture to start unit testing. That’s a mistake some developers make when they hear that a certain structure can make the code more testable. Then they wait until shifting to this new way of coding to start writing tests. Let’s not do that! If you want to test an existing code you will probably need to refactor some parts, but definitely not to change your architecture!

All about messages

Messages are the way objects talk to each other in an object oriented world. You pass a message to an object when you want it to execute a method. It’s the same to get a property value and that goes for everything else!

The process for knowing what to test has three simple steps and they all involve messages.

1. Identify the type of the message

There are two possible types of messages: Query and Command. It may be obvious but query is when you ask for something and a command is when you tell it to do something. A query has a return, but doesn’t change any state while a command has no return, but has some side effects.

query = returns something and changes nothing
command = returns nothing and changes something

2. Identify the origin of the message
There are three possible origins for a message: Incoming, Outgoing and Sent-to-self.

Incoming: When an object receives a message from the outside (another object).
Outgoing: When an object sends a message to the outside.
Sent-to-self: Guess what?! When an object sends a message to itself.

3. Follow this chart:

Image for post
Image for post
Unit Test Minimalist by Sandi Metz

Ooh! What a great chart! Right?! Keep this around until it becomes natural to you.

Applying the proccess

First example is an adoption of Equatable.

Incoming Query is pretty straightforward. You have an expected result, then you call the function and assert it if it returns what you’re expecting.

(The word sut stands for System Under Test. It will be used in the next examples too)


Second example: After loadView you wanna make sure your outlets were hooked right! They shouldn’t be nil at this point.

Incoming Command should be easy as well. Make sure the state changes to the expected value, after calling the function.

(The documentation says we should never call loadView directly, so we use loadViewIfNeeded to trigger it)

Before going to the next example I just like to point out that you may have a function that’s both a query and a command. In this case you should test it for both types of messages.


In this next example we have a function that changes the label based on the parameter.

It is important to define the proper access control for a function, it makes it easier to identify the possible origin. We don’t need to test it directly because we are already testing it indirectly when we test a public function that calls this private one.

It’s been easy so far, right?! Let’s see if it stays that way!


What we have now is a function that receives a value and sends it as a negative value to another object.

How do we test if a command was sent? You may try this:

However, we are not just asserting if the command was sent! We are testing that when we call addStorage, store adds the value to its wallet property. That’s a test for the LocalStorage itself, not for our ViewModel. In addition to duplicating code, we are totally coupled to the store implementation in our test and that’s not good. We should test our object in insolation!

The WalletViewModel only needs to know about the add(value:) function but it knows much more. To solve this problem we can create an interface for LocalStore and make our object depend on that.

Much better! Now the dependency will be injected and our object knows nothing about the implementation! In our test we can inject a dependency that only spies on it if addValue was actually called.

Now neither our test nor our object are coupled to LocalStorage and we only check if addValue was called (with the right value). We are using Dependency Injection here! I used the most simple approach, but not the best one. I advice you to read more about it and understand the other approaches.


We’ve covered the three messages we need to test and ignored the rest. However, I’d like to mention this last example to make the dependency point clear.

Although we should not test outgoing query, we should not depend on the implementation of our dependency! And for our tests, what if it hits the network to load the history? All other tests will need to wait until the history is fetched. That’s not good! Our unit tests need to run fast, and reaching the network can be really slow.

We need to use dependency injection again and inject a test version of TransactionViewModel when testing our object.

We’ve made TransactionHistoryViewController depend on an interface, created a test implementation for it and injected that into our tests. We will not test the outgoing query, but since it’s a dependency, we will use our test version to handle the possible returns.

Conclusion

I have shown you a process that I use for testing. It changed the way I test and helped make my code much more testable.

I tried to be generic enough so the knowledge could be applied to other programming languages as well. And I intentionally didn’t dive deep into some subjects, like Mocks/Stubs and Dependency Injection, because I think it would be too much.

I’m really glad I have finally finished this (long) post! It is still hard for me to write and it takes a long time for me to have a good outcome. I hope it helps and if you have any questions or feedback, leave them in the comments bellow!

Where to go from here

The full code for the examples shown
Where I first heard of Sandi’s method
The Magic Tricks of Testing by Sandi Metz
Engineering for Testability by Apple

HE:labs

Ideias e opiniões do time da HE:labs sobre desenvolvimento…

Thanks to Ronan Rodrigo Nunes and Marcelo Fabri

Daniel Souza

Written by

iOS Developer, learning and sharing in the process

HE:labs

HE:labs

Ideias e opiniões do time da HE:labs sobre desenvolvimento de software, design, negócios, gestão disruptiva e muito mais.

Daniel Souza

Written by

iOS Developer, learning and sharing in the process

HE:labs

HE:labs

Ideias e opiniões do time da HE:labs sobre desenvolvimento de software, design, negócios, gestão disruptiva e muito mais.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store