This content originally appeared on Level Up Coding - Medium and was authored by Vinodh Swamy

Unit testing is a type of automated testing meant to verify whether a small and isolated piece of the codebase the so-called “unit” behaves as the developer intended. An individual unit test is known as a “test case” — consists of a piece of code that exercises the production code in some way, and then verifies whether the result matches what was expected.
Why Unit Testing?
- Unit tests help to fix bugs early in the development cycle and save costs.
- Reduces fragility — making changes in the existing code breaks another working part of code or flow, this can be reduced and identified during the development stage using proper unit testing.
- Good unit tests serve as project documentation, a code snippet on tests explain more on how to use the class or function rather than a paragraph of documentation.
- Unit tests help with code re-use. Migrate both your code and your tests to your new project. Tweak the code until the tests run again.
Tips for writing test in Swift
There are 3 tips that I usually follow when writing tests in Swift.
- Design your code for testability
- Clear API Boundaries
- Avoid getting tied to Implementation Details
Design your code for testability
What makes a code easy to test?
Well according to me, when we are not doing a lot of mocks or hacks or complex setups just to test a single API behaviour then it's easy to test. something like you gives ActivitiesPayloadResponse struct to ActivitiesViewModel and gets back the viewData from it, it could be activities total count test case or unread activity titles.
But, we don’t have every class or function simple as giving input data and verifying the output, there are places where we need to do mocks and setups, but let us try to keep it minimal. On other hand, think of someone reviewing our test, he should not spend more than 30 secs to understand it.
3 core principles for Designing Code for Testability
Unified Input and Unified Output
In the functional programming world, there is a term called pure functions, pure function is a functions which takes inputs and returns an output with no side effects, the same input should generate the same expected output every time, no matter how many times you call it.
Keep the State Local
We tend to use singleton in most places because it's easy to use and apple itself is using it in many places, singleton can be nice for sharing API's and for convenience, but it also leads to a dangerous pattern of sharing state.
Dependancy Injection
Always try to inject the dependant objects which are being used inside our class functions, which means we want to avoid accessing those objects in our implementations as a shared instance or creating and accessing local instance.
That's enough of the theory, let's get practical, I cannot sell this topic without experimenting with it.
Let’s see all the above principles with an example class called FileLoader, credits to John Sundell picked these intuitive examples from one of his talks.

That's a FileLoader pretty decent class, it loads the file with the given fileName from the main bundle if exist else throws an error, also it caches the file, which is actually good, so we don't load from main bundle every time. Now let's apply the Design of Code for Testability to the above class and check if it's good for unit testing.
Unified Input/Output

The FileLoader takes fileName as input, and returns File if the file with filename exists in the main bundle, else throws. But if you see the implementation there are 3 possible outcomes from the function.
- The happy case where a file exist, returns the File loading it or from the cache.
- Throws foundationKit error if the file is corrupted when converting the file to Data
- We throw NSError if the file doesn’t contain the main bundle.
Definitely, the output is not unified here, this is not satisfying the Unified Input/ Output principle
Keep the State Local

The FileLoader contains shared instance or singleton dependency as you can see Bundle.main and shared FileLoader, we are not keeping the state local. As is clearly evident that the function is not obeying the Keep the State Local principle.
Dependency Injection

The FileLoader depends on 2 objects here, one is the cache and the other in the Bundle, and as you can see none of them is injected as dependency, it is accessed locally or using a singleton, which will kind of makes us difficult to test.
That's interesting right our decent FileLoader class is no more decent for unit testing, so let see how can we make it obey the Code for Testability
Applying Code for Testability Principles
Let us take it one by one, will try to make the FileLoader obey Unified Input/Output principle.

Here if you see, there is a dedicated error type for FileLoader, where it returns FileLoaderError.invalidFileName for invalid fileName error and FileLoaderError.invalidFileData for corrupted file data, whereas previously we were returning the NSError or the Error is thrown from the try Data(contentURL:), this way have modified the code take unified Input (FileName String) and unified Output (File or FileLoaderError).
Next let's try to remove the shared instance, do we really need
static let shared = FileLoader()
Many times we use it because it is easy to access and use, if we think, we might not need a singleton here instead every class that needs it can create and get the file loading operations, or it can be passed as a reference to the classes where it is required.
The last one the Dependancy Injection, as pointed out earlier we have 2 dependency objects (Cache and Bundle) used by FileLoader.

With this change, we are passing the dependant objects as an initial parameter so this will be super helpful when we are writing unit test for the same, this doesn’t have to be the object itself, it can be 2 protocols if required and also since we are supporting default parameters, the users of FileLoader other than Unit Testing need not worry about injected parameters, they can still create just as FileLoader().
We did very minimal changes to code, now kind of the FileLoader has changed from Hard to Test → Easy to Test. Now that I claim it's easy to Test you guys trust me? Not required let's write some tests for FileLoader and see is it really easy to Test.
We will try to test caching behaviour of our FileLoader couple of ideal behaviours coming to mind are
- Save file to cache when loading the file first time from bundle
- Retrieve a file from the cache when the file already exists in the cache.

There can be many cases, just picking these to explain stuff related dependency injection of cache.
- The 1st test case is for unit testing if the file loaded by FileLoader is saved into cache or not. Because our FileLoader’s one of the cool features is, it caches the files once loaded from Main Bundle.
- The 2nd test case is for unit testing if the file accessed by FileLoader is retrieved from cache or not, say we have dependant cache containing the same file already and injected to FileLoader, now if the file is accessed by FileLoader it should return the file from Cache instead of loading from the main bundle.
Clear API Boundaries
When we usually write a test, we tend to write a couple of types, 1 is a unit test where we verify if the API works correctly or not, validating the output against the given input, the other test is where we actually integrate modules and do an integration test.
Many times it is difficult to identify the clear boundary between unit and integration tests. If we look at Michael Feathers (Author of Working Effectively with Legacy Code) definition of Unit Testing.
A test is not a unit test if:
- It talks to the database
- It communicates across the network
- It touches the file system
- It can’t run at the same time as any of your other unit tests
- You have to do special things to your environment (such as editing config files) to run it
Ok, Ok, Thats fine, but why?
Here is what his explanation are.
First, speed. When it comes to unit testing best practices, speed is the most important of them. Relying on things like the database or the network slows tests down.
Then, we have determinism. A unit test must be deterministic. That is to say, if it’s failing, it has to continue to fail until someone changes the code under test. The opposite is also true: if a test is currently passing, it shouldn’t start failing without changes to the code it tests. If a test relies on other tests or external dependencies, it might change its status for reasons other than a code change in the system under test.
Finally, we have the scope of the feedback. An ideal unit test covers a specific and small portion of the code. When that test fails, it’s almost guaranteed that the problem happened on that specific portion of the code. That is to say, a proper unit test is a fantastic tool for obtaining super precise feedback.
If on the other hand, a test relies on the database, the file system, and another test, the problem could be in any one of those places.

As shown in the above picture, there are 2 modules User Profile Module (UI related to UserProfile) and User DB Repo (Database Module) which are been tested individually with inputs and validated using asserts.
When we are unit testing the UserProfile Module, definitely we would not be using the UserDBRepo instead we mock the UserDBRepo interface in UserProfile Module and unit test, which way we are not making DB or file system calls.
Again when unit testing the UserDBRepo Module, we would be injecting in-memory mock DBHandle which handles DB CRUD operations, using which we can unit test all possible APIs for UserDBRepo Interface.
But when it comes to testing UserProfile Module using UserDBRepo, this is usually not a unit test it is an integration test, and it's not that we should not write these tests at all, it would be good to write these tests, but often these tests are slow and not reliable, when the project grows, these integration test will consume a lot to time to build and run the entire test suite, and sometimes if the test fails, we are not sure which module has caused the failure.
Avoid getting tied to Implementation Details
Testing behaviour means testing what code does. Testing implementation means testing how code works. So which one we should be testing?
Testing Behaviour OR Testing Implementation
Unit Testing On Behaviour
When you write tests for behaviour, you can refactor your code (the implementation) in confidence without breaking the tests, the tests only break when behaviour has changed (unintentionally — which means you’ve introduced a bug or intentionally code has new changes, which means tests need to be updated to the new behaviour).
These types of tests are extremely valuable because a broken test means your code is broken.
Testing behaviour guarantees your code works as expected (for the behaviour you have tested).
Unit Testing on Implementation
When you write tests for implementation, tests break when you change code, even if the behaviour didn’t change.
These types of tests aren’t very valuable because a broken test doesn’t mean your code is broken and a passing test doesn’t mean your code works as expected.
Testing implementation only guarantees your code was written as it was tested.
Applying Unit Testing on Behaviour of function/class instead of Implementation.
Ok let us see this with an example, let consider simple class ImageViewController


That’s our ImageViewController, it has a dependant ImageLoader which loads the remote web images, and the ImageViewController sets the downloaded image to its imageView.
Now let's see what is the implementation steps looks like
- Step 1 ImageViewController creation
- Step 2 Call loadRemoteImage from ImageLoader via reloadImageView for downloading images from remote web URL.
If we write the test for the above implementation steps?

Ok, we have mocked the ImageLoader to MockedImageLoader, yea that's there because we are loading the image from a network call, so understandable. We now inject the same to ImageViewController and call reloadImageView, later we check if the mockImage given to MockedImageLoader is loaded into loadedImages set or not. That's fair, that's exactly what has been implemented.
That seems like a unit test, but are we really testing what the ImageViewController suppose to do? Oh! No! the ImageViewController suppose to load the image from the remote URL and display it in the imageView. But we are rather unit testing logic of ImageLoader is loading given image or not, that's because we wrote the test with the implementation details in mind, that's why we ended testing the ImageLoader rather than the ImageViewController itself.
Now lets us try to write a test for ImageViewController reloadImageView behaviour.
Let us tweak our ImageLoader a little so we can write the unit test for the ImageViewController behaviour.

Now will see the unit test for verify the ImageViewController’s reloadImageView behaviour.

Interesting right?
We have not mocked the ImageLoader, yea we don’t need to, we are verifying the outcome of reloadImageView which is checking if ImageView.image is what sent via mockImage, and one more advantage is we don’t need to update the unit test, even though we modify the implementation of ImageViewController and ImageLoader, until the behaviour of ImageViewController reloadImageView is same.
That's a very simple example of writing a unit test to class/functions behaviour instead of implementation.
Art of Unit Testing with Swift was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Vinodh Swamy

Vinodh Swamy | Sciencx (2021-11-30T03:48:24+00:00) Art of Unit Testing with Swift. Retrieved from https://www.scien.cx/2021/11/30/art-of-unit-testing-with-swift/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.