[Tutorial] Part 3 — VIPER design pattern: what, when, why and how

In this tutorial, let's take a look into the Viper design pattern, apply it to a Todolist application, and run some unit tests.

This post is part 1 of the tutorial.
Check part 2 to see VIPER in action.
Check part 3 to learn how to do unit tests with VIPER.

One of the biggest advantages of VIPER architecture is the testability it provides. By having the process of information happens in the Interactors, we can focus our testing on that component, making sure that the logical operations are working properly and returning the expected data.

Taking our todo list application as an example, let’s look at the SplashInteractorTests and understand how can we create a dummy Interactor with a proper interactorDelegate simulating the Presenter to create an Interactor-To-Presenter bridge.

This is one of the many possible approaches to how tests are implemented on VIPER, so make sure to analyze some of them and choose the one that you are more comfortable with.

-- CODE language-js line-numbers --
class SplashInteractorTests: XCTestCase {    // 1<br>
   &nbsp;private var interactor: SplashInteractorType!<br>
   &nbsp;private var expectation: XCTestExpectation!<br>
   <br>
   // 2<br>
   &nbsp;override func setUpWithError() throws {<br>
    &nbsp;&nbsp;   self.interactor = SplashInteractor()<br>
    &nbsp;&nbsp;   self.interactor.interactorDelegate = self<br>
  &nbsp; }    // 3<br>
   &nbsp;&nbsp;override func tearDownWithError() throws {<br>
   &nbsp;&nbsp;    self.interactor = nil<br>
&nbsp;&nbsp;self.expectation = nil<br>
   &nbsp;}    // 4<br>
   &nbsp;func testTimeout() throws {<br>
&nbsp;&nbsp; // 4.1<br>
    &nbsp;&nbsp;   self.expectation = XCTestExpectation(description: "PerformTimeout")<br>
    &nbsp;&nbsp;   // 4.2<br>
&nbsp;&nbsp;self.interactor.performTimeout()<br>
     &nbsp;&nbsp;  // 4.3<br>
&nbsp;&nbsp;self.wait(for: [self.expectation], timeout: 3.0)<br>
  &nbsp; }<br>
}extension SplashInteractorTests: SplashInteractorDelegate {<br>
  &nbsp; // 5<br>
  &nbsp; func onTimeoutPerformed() {<br>
   &nbsp;&nbsp;    self.expectation.fulfill()<br>
  &nbsp; }<br>
}

This is a straightforward example containing only one test, the testTimeout test, but the rest of the configuration is very similar between all the other tests, containing an Interactor and the interactorDelegate. Let’s go through each step, one-by-one to fully understand what’s being done.

1. Our variables

Most of the cases all you need are these two variables: Interactor and XCTestExpectation. The first one is the Interactor that we are testing, no mystery here. The second is a little bit more special.

XCTestExpectation gives us the ability to perform asynchronous tests by creating an expectation and asking our test to wait a certain time for that expectation to be fulfilled. We’ll explore that further.

2. Setup

setUpWithError runs every time right before a test is executed, so it’s good practice to initialize the Interactor here. Furthermore, you need to set the interactorDelegate. By setting the test as the interactorDelegate, it becomes the Presenter and will be expecting a response from the Interactor. This is how we create fake Interactor-To-Presenter communication.

3. Tear Down

Similarly to setUpWithError, tearDownWithError is executed right after each test has finished. It’s common to destroy our variables here before jumping to the next test. The action of creating and destroying the Interactor between every test is to make sure that any tampering that we do to the Interactor in the middle of a test does not have consequences on the next test.

4. Our test

Each function on the SplashInteractor should be represented here as a test, to properly test the whole Interactor. In our case we only have the performTimeout function, so that’s what we are going to test.

  1. Start by initializing the expectation. This is the XCTestExpectation that we talked about previously that allows us to do asynchronous testing. Give it a description that briefly identifies what you are testing.
  2. Run the Interaction action we are testing.
  3. Notify the test that it should wait for the expectation to be fulfilled. Additionally, define the maximum amount of time that expectation should take to be completed. If it exceeds that time, the test will fail, so this is also a good way to test if some API request is taking more time than it should for example.

5. Interactor result

When the Interactor successfully returns the result, we’re going to catch it on the Test, since we set the interactorDelegate as the Test and not the Presenter as it normally is. After receiving the correct result, we say that the expectation is fulfilled. This will trigger the continuation of the test execution phase and the next test will begin.

If by any change the result is not the expected one, we could do the following:

XCTFail("performTimeout failed")

This will immediately mark the test as “failed”. Give it a good description that will tell us what error failed, in case this is running on a remote continuous integration tool.

Creating tests on VIPER is very easy and straightforward, as we already have the logical part isolated in a single component, the Interactor. In comparison to other patterns, like MVVM that can take some time to be properly configured before running your tests over the ViewModel, since it’s responsible for more than just processing information, or MVC which is even worse in terms of following the single responsibility principle, VIPER can be a breeze, showing that although you can lose some time getting the architecture up and running, you will gain time ahead in other essential parts of a project.

And that covers how to create Unit Tests when using the VIPER architecture! Feel free to go back to part 1 to learn more about VIPER design pattern, or part 2 to see it in action.

If you enjoyed reading this post, please give us some Claps below! ❤️👇

Also, don’t forget to follow Pixelmatters on Medium, Twitter, Facebook, LinkedIn, and Instagram.

Filipe Santos
Mobile Developer

Because, every pixel matters.

Let's talk