The Foundations of Unit Testing (3/3): Effectively testing the MVC pattern in Swift
credits: xkcd

In the last article, we’ve done our best to write proper MVC. But we’re all about testing, so here’s our approach to it.

So, first things first, let’s test our model! This should be fairly easy because it does not have any dependencies (notice that we don’t have a dependency injection method — e.g. setSomething — stated in the model’s protocol), hence, it is an isolated module by its own nature.

So, let’s dive in:

First, we need to instantiate the star of our show. It’s gonna be the main subject of all our test cases. Here’s a naming pattern that I like to apply for test cases. As stated, is the “test<System Under Test>_<Condition Or State Change>_<Expected Result>” naming pattern. I like it cause it gives a quite clear description of what’s going on and what’s expected in each test.

Also, while writing the code for the test itself, I like to apply this “Given, when, then” pattern. It makes very clear and readable what every test is doing and trying to accomplish.

If you are also creating the classes presented in this article, feel free to run the tests and see the results — hopefully all of them will pass!

In order to keep this tutorial short, I’ll not cover UITests in it. Hence, I’ll not test GumballView functionalities, but I encourage you to do so.

But, nonetheless, let’s head for ViewController tests.

Here’s where dependency injection REALLY shines. Let’s dive in!

As usual, we isolate our test subject — in this case, the view controller. Here’s the big deal behind dependency injection, protocols and relying upon abstractions. Since the only thing the view controller know about its dependencies is the fact they implement a specific protocol, we can mock their behavior by implementing mock classes (classes that adhere to the protocol). This way, we can truly test in an isolated, decoupled manner! Take a look at the mock implementations of the view and the model. Due to them, we don’t need a fully-fledged view or model to test our view controller with! Please, take some time to absorb how powerful this concept is — see how through it, we are able to completely isolate and test only the controller functionalities. The setUp method is called upon the beginning of every test. Hence, we use it to inject our mocks in the view controller. Similarly, the tearDown method is called upon the end of every test. We use it in order to reset our mocks variables to their default values.

If you are following the tutorial by yourself, run the tests! Also, if you want to see the final result, there’s one last thing to do.

In order to finish it up, in the Main.storyboard file, set the view controller class to GumballViewController.

You might be asking yourself: “Hey, what’s the purpose of storyboards if we are not setting outlets to their view controllers?”

The answer is quite straightforward, actually: use storyboards for navigation (e.g. adding navigation controllers, tab bar controllers…) and not for designing your view (and consequently bloating up your controller with outlets).

It’s a wrap, guys! Let’s take a look in our final result:

So yeah, by the end of this article, I sincerely hope that you’ve changed your opinion about the MVC pattern and have found flaws in the way you apply it. By the way, by any means I’m pledging that this is the only correct way of applying the MVC, but definitely is a way that effectively apply SOLID principles in order to turn the pattern testable.

If anything, I hope from the bottom of my heart that you’ve absorbed these key points:

A model IS NOT a data container

a data container A controller shouldn’t be so bloated (e.g. a view controller class with 400+ lines of code is definitely a huge indication of code smell)

Rely upon abstractions (through protocols). Also, try to make the best out of it by mocking a dependency behavior while testing,

Thanks for reading! Hope you’ve enjoyed these articles and learned something along the way about unit testing. See ya!
['João Pedro De Amorim']
['Unit Testing', 'Xctest', 'Test', 'Oop', 'Swift']