A Complete guide to Unit Testing in Swift- Part III

Unit Testing async operations, closures & delegates

Nishan
4 min readJun 29, 2019
Photo by Émile Perron on Unsplash

Part I: https://medium.com/@nishan/a-complete-guide-to-unit-testing-in-swift-part-i-4dd4c37926dd

Part II: https://medium.com/@nishan/a-complete-guide-to-unit-testing-in-swift-part-ii-d714b43da07c

Till now we have covered the basics of Unit Testing, writing testable code, creating mocks and use of mocks in Unit Testing. This part will focus on writing tests for asynchronous code.

Unit Testing & Asynchronous Operation

In real scenario, lots of code that we write are asynchronous. You might be fetching things from api or processing things in background thread and after the task is complete, you notify its completion using either delegates or closures.

Writing Unit Tests for these types of asynchronous operation is slightly different than what we did before as these operations do not execute directly within the current flow of code.

Lets change the mock implementation so that fetchDataFromApi()” of our LabelDataServiceMock so that it executes its closure asynchronously after 1 second. If you run the previous test again, you will find that one or more tests fails.

In order to write test that passes for above code, we must find a way to wait until the response is received from “fetchDataFromApi()” method.

XCTestExpectation: To Expect Or Not To Expect

We use expectation to wait incase of asynchronous code. As the name itself suggest, expectation waits for something to happen. It either gets fulfilled or doesnot get fulfilled. When expectations doesn’t get fulfilled, our test fails. Lets refactor our unit test to handle asynchronous operation.

When we replace our previous unit test with the one above, we can see that our test passes. Lets understand what’s going on

  1. First we create an expectation. Its an instance of XCTestExpectation available in XCTest Framework. Write meaningful description of your expectation. We want it to be fulfilled exactly 1 time, so the fulfillment count is 1.
  2. Whenever success closure gets executed, our expectation gets fulfilled. Thus we execute “fulfill()” method of our expectation. We also assert that the data should not be nil
  3. We wait for our expectation to get fulfilled by executing “wait(for: timeout:)” method. Test operation waits until the expectation gets fulfilled or until the timeout limit is exceeded. Since our mocks performs async operation after 1 second, our expectation gets fulfilled within timeout limit.

Testing Delegates & Closures

So far we have learned about testable code, creating mocks and writing unit tests for both asynchronous & synchronous operation. Now lets put all the things that we have learned till now by writing unit tests for a real world scenario, where different business logics interacts with each other using delegates & closures.

Suppose we are building a simple food ordering app where user types the name of the food items that he would like to order. These names would be stored in an array. When user places an order, these list would be sent to the server and server would tell whether the order was placed or not. We will implement above scenario using simplified M V P (Model-View-Presenter) design pattern.

Our app flow diagram
OrderViewController: A UIViewController subclass

Basically our “OrderViewController” contains all the views and outlets and actions. It communicates with “OrderViewPresenter” for all business logics such as adding an order or placing an order. Instead of creating the instance of Presenter inside viewDidLoad() method, we can inject it using the concept called Dependency Injection.

Our Controller conforms to OrderViewDelegate protocol which is how presenter communicates back with Controller.

OrderViewPresenter: Our business logics

“OrderViewPresenter” is where our business logic resides. It communicates with “OrderService” to actually send an order request to server. It then communicates back with our viewcontroller using delegate pattern. There isn’t any reference to our view (OrderViewController) inside presenter. In a sense, our presenter doesnot know anything about our view. This loose coupling will ease us to write tests later.

OrderManager: Class which conforms to our OrderService

“OrderService” is similar to the LabelDataService class that we learned in Part-II of this series. A concrete instance of OrderService i.e OrderManager is injected during initialization in presenter.

Writing Tests

In real scenario, we tend not to write unit tests for our view related code. We do have a separate concept called UI Testing which enable us to test our view. Since we have decoupled our view related code from our main logic, we will not be writing any tests for “OrderViewController”.

Lets start with writing mock for OrderService. Its similar to the mock we created for LabelDataService.

Go on and write test cases as you wrote previously for LabelDataService. I’ll skip it this time.

Our “OrderViewPresenter” requires two things to operate.

  1. Implementation of OrderService which is injected during initialization. Our newly created OrderServiceMock would is sufficient for this.
  2. Implementation of OrderViewDelegate. Lets create a mock for this too.

Now we have everything to write the tests for our Presenter. Instead of using real delegate & service, we inject the mock created for both delegate & service. As a result, we can test this presenter as a single independent unit. Lets write test cases for OrderViewPresenter.

Run the tests and you can see that all of them passes. ✅✅✅. Thanks to our tests, now even if another Bob joins the company and refactors the code, any mistakes that he makes in this flow will be caught early. That’s the beauty of Unit Testing.

This is the end of this series. I hope this unit testing guide has provided all the necessary information required to start Unit Testing your codebase. Cheers!

--

--

Nishan
Nishan

Written by Nishan

Code, Eat, Sleep, Travel, Repeat

No responses yet