# Unit Testing Composable Routing in Swift for iOS Apps - Part 3


> This is the third part of a [series of articles about a Clean, Simple and Composable Routing for iOS apps](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od). Have a read at the other articles before starting this one 😊

We setup our routing system by resolving the dependencies using a [Dependency Container](https://github.com/CassiusPacheco/DependencyContainer) in the routes, as you've seen in the [previous articles](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od). Now it's time for us to write tests for them and validate how isolated and reusable our routing system is.

# Automated Tests

Automated testing is finally picking up in the iOS community. Ten years ago, when I started in iOS development, one would barely hear about automated testing, the platform itself would provide few poor tools for it, and Apple wouldn't push us much for writing them either 🤐. 

Five years later, around 2015, many job descriptions would still state that "experience with unit testing is desired" or "automated testing experience is a plus" 🤔.

Fast-forward to the present, I can't remember seeing job descriptions nowadays that don't require solid experience with automated testing ([unit testing, integration testing, UI testing, etc](https://martinfowler.com/articles/practical-test-pyramid.html#TheTestPyramid)) and [continuous delivery](https://en.wikipedia.org/wiki/Continuous_delivery). We can't escape from it anymore (well, we shouldn't anyway!) 😬.

**Test automation reduces efforts for manual testing, increases predictability, reliability and resilience of the project**. More importantly than validating the code you just wrote, is giving you and the team the confidence to update or extend it in the future, knowing that your business logic is being validated by automated tests.

Good test suits also help onboarding new engineers to the source code, or even remind your future self of the work done in certain parts of the application, as it may help you all understand better what's expected from the code by reading the tests.

There are many articles about how [writing tests made people better developers](https://www.seeleycoder.com/blog/how-unit-testing-made-me-better-developer/). This is manly because in order to make your code testable you must design/architect it properly, by injecting dependencies, isolating responsibility, minimising state manipulation, writing more pure functions, etc. Applying these concepts not only make your code testable, but easier to maintain, extend and update. It's a [win-win strategy](https://en.wikipedia.org/wiki/Win-win_game)!

If you're new to testing, especially in iOS, watch this 2017 WWDC session about [Engineering for Testability](https://developer.apple.com/videos/play/wwdc2017/414/) and read a long list of great articles by John Sundell about [unit testing](https://www.swiftbysundell.com/tags/unit-testing/).

Outside the iOS world (hey, we're all Software Engineers here, not only iOS Developers, right? 🤗), I can't recommend enough [Martin Fowler's articles about testing](https://martinfowler.com/testing/). Martin is a reference in the software engineering community for his great posts about testing and architecture. Don't miss out!

# Creating Mocks

Alright, back to where we left our project in the [previous article](https://cassiuspacheco.com/applying-dependency-injection-to-composable-routing-in-swift-for-ios-apps-part-2-ck7v10xaz01h9zis1oksoe7h0), we could begin our work here by unit testing the main piece of the system, the `DefaultRouter`.

We want to have full control of our unit tests, being able to spy on the methods and properties which are being called by our class to validate its behaviour. In order to achieve that, we need to create "fake classes" (which I'll call `mocks` in this article for the sake of simplicity, but [understand that there are many names and responsibilities for these doubles](https://martinfowler.com/articles/mocksArentStubs.html)) by implementing `protocols` or subclassing objects.

Looking at the `DefaultRouter`'s initialiser you'll notice that it takes a `Transition` interface (A.K.A. `protocol`) and a `DependencyContainer`. Interfaces are easy to mock, since we just need to implement their required methods and properties, but the container will be trickier, though, since it's a concrete object. If a `route` tries to resolve a dependency that's not registered in the `container` the `route` is holding, the tests will crash 😰. 

There are a few solutions for this problem, for example, we can make the container conform to a new container protocol, so we can mock it properly, or we can register mocks for all dependencies in the app when running the tests.

I usually prefer to use [interfaces instead of concrete objects](https://en.wikipedia.org/wiki/Interface_segregation_principle) because it makes the code more flexible, testable and conforms to [SOLID](https://en.wikipedia.org/wiki/SOLID) and other good practices, but in this case in particular I don't. 

`Routes` are the pipes of our a dependency graph's plumbing system that inject values in the controllers being presented. This means that regardless of having a mocked container or a real container instance, we need to make this container return the correct interfaces that the route needs. In the example below for our current `LoginRoute`, an instance that conforms to `LoginViewModelInterface` is expected to be returned by the `container` on line 17.

![01-loginroute.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584961636582/bEhOl_iHU.png)

Creating an interface for this container in order to completely mock it will bring little value to us, it'll be easier to just to register the mocked dependencies directly to the real container, since the container framework is so simple.

# Registering Mocks

In this sample application only view models are registered in the container. The same interfaces registered in the [DependencyGraph.swift](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExample/DependencyGraph.swift) must also be registered in the container created for the unit testing target.

There are many code generation tools for generating mocks from interfaces in Swift. We will not use any of them in this sample app for the sake of simplicity, but I strongly encourage you to have a look and use them in your app, since they save you **a lot of time** writing boilerplate code. A few of them are [Mockolo](https://github.com/uber/mockolo), [Cuckoo](https://github.com/Brightify/Cuckoo) and [Sourcery's AutoMockable](https://github.com/krzysztofzablocki/Sourcery).

All our manually-created view model mocks will follow the pattern below:

![02-popupviewmodelmock.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584962537104/pKWAKYRdi.png)

They implement the required methods by the interface and store how many times the methods were called. If the methods have input arguments, we store them too for further validation in the tests. The same applies to return values, where we define `underlying` values that'll be used as the returned values, as you can see on lines 6 and 9. Underlying values give us flexibility when writing unit tests to quickly change the expected return value of our mocks.

Once all view models mocks are created in the Unit Tests target, it's time to register them in our `DependencyContainer`. I like to create an `extension` for the container that's only visible in the unit tests target, to make it convenient to access the container with all dependencies mocked.

![03 - mocked container.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584962813478/TmN3F3_lo.png)

It's a real `DependencyContainer` instance, but all registered interfaces return mocked values when resolved👌. 

> Note that's important to keep the same arguments signature here, even though they're not being used by the `Mock` class, because the `DependencyContainer` framework needs them to resolve the dependencies correctly.

# Testing the `DefaultRouter`
The last step before testing the `DefaultRouter` is to also write a mock for the `Transition` interface. Here it goes:

![04 - transitionmock.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584968326821/KEu4npXN3.png)

It's the same pattern applied for the view models.

I find it very convenient as well to add extensions to the class that's going to be tested with default mocked values being assigned to its initialisation. In this way when adding new dependencies to the class, you don't need to update all tests, just the helper and the tests that may use the new dependency. Have a look at line 26:

![05 - DefaultRouterHelper.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584968806489/x1tqss1cA.png)

On line 6 I'm making use of this [UIViewController subclass](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExampleTests/Supporting%20Files/Mocks/Others/ViewControllerMock.swift) that allows me to validate which view controllers have been pushed, presented, dismissed etc.

You've probably noticed as well that there are heaps of tests for this class. In the spirit of the unit testing, I've broken down each possible behaviour in a different test, making them very comprehensive. You can look at the full implementation [here](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExampleTests/Routers%20Tests/DefaultRouterTests.swift), but for now here's a couple of them:

![06 - DefaultRoutertests.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1584969247512/zGaBHsPZd.png)

The first test validates that `route(to:as:)` does not open any screen when the `router`'s `root` is `nil`. It asserts the `root` on line 15, as well as asserting that the `transition` mock has not received any method call. Simple, isn't it?

The second test then validates that `route(to:as:)` **does open** the screen when the `router`'s `root` is **not** `nil`. We assign an instance of `ViewControllerMock` as the `router`'s `root`, then call `route(to:as:)` and assert that the `transition` mock has recorded the open view controller call, and finally assert that the values passed in to the transition mock from the real `router` instance are the ones we expect 👏.

Finally, in the third test sample, instead of routing to view controllers using transitions, we're testing the close method, which closes the current `router`'s `rootTransition`. It's just as simple as before, setup the values from lines 34 to 36 (note the use of `makeDefault` passing an argument, without having to pass the `container`. Our helper does that for us), call the method to be tested on line 38 and assert everything from lines 40 to 42.

Testing the `DeeplinkRouter` is just as simple. The source-code is available [here](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExampleTests/Routers%20Tests/DeeplinkRouterTests.swift). I bet you never thought that testing deeplinking would be so simple 🤗!

# Testing Routes

Since our `Routes` are just protocol extensions from the `Router` protocol, all we need to do is to create a `RouterMock` that conforms to the same protocols our `DefaultRouter` does.

![07 - TestRouter.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1585034151812/8_1DG23N-.png)

> I've omitted the methods with completion handlers from the code above to make it simpler, but you can have a look at the full implementation [here](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExampleTests/Supporting%20Files/Mocks/RouterMock.swift).

Don't get confused thinking that we're testing a mock class just because we're using the `RouterMock`, because we aren't. What we are testing in the code below is the [LoginRoute](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExample/Routing/Routes/LoginRoute.swift) that's defined as a protocol extension that we made `RouterMock` conform to.

`RouterMock` just provides helpers for us to spy on the methods called from the `LoginRoute` (and other routes) defined in the protocol extension.

![08 - routermock.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1585034705905/lmhOrRvPe.png)

In the code above, we first included a new `extension` helper to our Unit Test Target that makes it easier to return directly the `rootViewController` of a `UINavigationController` instance, for cases where we're opening a screen embedded in a navigation controller (which is the case of the `LoginRoute`).

Then we wrote a test that ensures:

1. the screen in being opened only once by asserting `router.routeToCallsCount == 1`.
2. the presented view controller is a navigation controller with an instance of a `LoginViewController` embedded in it by asserting `router.routeToReceivedVC?.asNavigationsRootVC() is LoginViewController`.
3. the transition used for opening it is a `ModalTransition` by asserting `router.routeToReceivedTransition is ModalTransition`. 

Thanks to fact that we're including the `DependencyContainer.mock()` as the default container in the `RouterMock`, it ensures the view model being resolved in the `route` is also mocked. That frees us from any side effect that the real instance of the view model may have caused by running this test (think of network calls, etc.).

All routes tests can be seen [here](https://github.com/CassiusPacheco/iOS-Routing-Example/tree/part3-di-tests/RoutingExampleTests/Routes%20Tests). Easy, eh? 🤯

# Testing ViewModels

To finish off our unit tests we need to write some for our view models. This app is very simple and the only side effect it causes is presenting new screens, but bear in mind that this architecture is so simple that even if it was a real application it wouldn't be much harder than what we're going to do soon.

Each view model expects a combination of `Routes` that they define in the form of a `typealias`. For example, in the `ProductViewModel` it looks like `typealias Routes = ProductRoute & PopUpRoute & SiriRoute`.

Similarly to what we did with `RouterMock`, we need to create a new mock that implements all `Routes` interfaces, instead of using the real `protocol extensions`, so we record which routes have been called from the view model.

![09 - routesmock.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1585035912077/rWn50PRGP.png)

> The code above only shows a couple of `Routes` in the mock, for the entire implementation have a look [here](https://github.com/CassiusPacheco/iOS-Routing-Example/blob/part3-di-tests/RoutingExampleTests/Supporting%20Files/Mocks/RoutesMock.swift).

Now since `RoutesMock` implements all routes of the app, we can use it to pass in as an argument to all our view models in the tests!

![10 - product view model.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1585036300716/ytbaLAtYp.png)

In the three tests above we're able to ensure that:

1. `productButtonTouchUpInside` triggers the `openProduct` route method in the `router` given.

2. `wishlistButtonTouchUpInside` triggers the `openPopUpWithMessage` route method in the `router` given passing the message `"Product added to the Wishlist!"` as an argument.

3. the `siriButtonDelegate` is actually the `router` itself (since `SiriRoute` handles the delegate).

The rest of the view models tests can be seen [here](https://github.com/CassiusPacheco/iOS-Routing-Example/tree/part3-di-tests/RoutingExampleTests/ViewModels%20Tests).

# Conclusion

Phew, we've come to the end of a lengthy [series of posts about Composable Routing](https://hashnode.com/series/clean-simple-and-composable-routing-for-ios-apps-ck7vm42k401n4zis1wu4ar2od), but hopefully it's clear now that a simple but powerful architecture makes our project easy to understand, extend and test!

The [source-code](https://github.com/CassiusPacheco/iOS-Routing-Example/tree/part3-di-tests) of the project is available on my Github, with all the implementations done.

Thanks for reading and let me know what you think. Feel free to message me up on [Twitter](https://twitter.com/cassiuspacheco) too!
