Clean Swift TDD Part 4 – Presenter

In this post, you’ll complete the VIP cycle by testing the presenter for the fetch orders use case. You can read about my previous posts on testing the view controller, interactor, and worker.

The key takeaway here in this post is this. The job of the presenter is to format data for display to the user.

Our Order model’s date attribute is an NSDate and total is an NSDecimal. But the labels in the table view cell we use to display information to the user take Strings.

Your view controller and views do not need to know how the date and total are represented in the app. They just need some strings to display to the user. You’ll see how to format the order date and order total in the presenter, such that your view controller is agnostic to the data representation.

If you later decide to store the order date and total differently, you just have to modify the presenter while the view controller can remain intact. And it’ll be easy to unit test because there’s a clear input and output.

You can even extract the formatter to higher level so that it can be reused in the rest of the app where you need to display the order date and total in the same format. It is a great feeling to know that you just need to find the formatter and make changes in one place while you’re well covered with unit tests.

Isolate the dependencies

You are testing the ListOrdersPresenter and it has one external dependency – ListOrdersPresenterOutput. So let’s mock it out by creating ListOrdersPresenterOutputSpy.

You want to make sure when you invoke the presenter’s presentFetchedOrders() method, the output’s displayFetchedOrders() method is invoked as a result. If that’s the case, the displayFetchedOrdersCalled variable will be set to true.

You also want to make sure the presenter is formatting the order properly for display. So let’s save the view model passed in to the displayFetchedOrders() method to the listOrders_fetchOrders_viewModel variable. You can write assertions to inspect it in the Then of your test case.

Okay, but how should the ListOrders_FetchOrders_ViewModel look like?

Remember, you want to make your view controller agnostic of the data representation of an order in your app. So you don’t want any reference to Order, NSDate, or NSDecimal. The labels take Strings. So let’s create a new DisplayedOrder struct to represent an order for display.

The DisplayedOrder struct is only needed in the view model, so you can just stick it inside the ListOrders_FetchOrders_ViewModel struct. Thanks Swift!

What order information will a user likely need to know? That’s what you’ll put in DisplayedOrder If your Order model contains data such as fulfillment date, warehouse location, lot number, …etc, refrain from putting them in DisplayedOrder. A user doesn’t need to know how the business operates internally.

I also use name instead of separate first and last names in DisplayedOrder since formatting names is the job of the presenter. You’ll likely do name = firstName + a space + lastName in your presenter when you format an Order into a DisplayedOrder. But let’s not jump too far ahead. Let’s write your test first in the spirit of TDD.

Finally, you are fetching a list of orders, so make displayedOrders an array of DisplayedOrder in the ListOrders_FetchOrders_ViewModel.

Write the test first

You want to test for two things with the presenter’s presentFetchedOrders() method:

  • Format fetched orders for display – convert [Order] into[DisplayedOrder].
  • Ask the view controller to display the formatted orders – invoke the displayFetchedOrders() method on the output.

Let’s write the first test now:

First, you want to use the ListOrdersPresenterOutputSpy you created earlier in place of the actual output.

Next, create a known date, then an array of one test order, and a ListOrders_FetchOrders_Response object with the test order array.

When you invoke the presentFetchedOrders() method with this response, you can now iterate on the displayedOrders in the view model to make sure the id, date, email, name, and total are formatted properly as strings for display to the user.

Let’s move on to the second test:

Like the first test, you set up the ListOrdersPresenterOutputSpy and the ListOrders_FetchOrders_Response with an array of one test order.

When you invoke the presentFetchedOrders() method, you simple want to make sure the ListOrdersPresenterOutputSpy’s displayFetchedOrders() method is invoked. This’ll ask the view controller to display the orders you have just verified to be properly formatted in the first test.

Draw the boundary

The boundary is again pretty simple. You just need to add the displayFetchedOrders() method to both the ListOrdersPresenterOutput and ListOrdersViewControllerInput protocols.

For now, just add an empty implementation of displayFetchedOrders() in ListOrdersViewController to make the compiler happy. You should focus on testing the presenter and not worry about the details of how to display an order to the user.

Implement the logic

You wrote tests to make sure the presenter:

  • Format the orders for display
  • Ask the view controller to display the orders

Let’s satisfy those assertions now:

It’s expensive to create NSDateFormatter and NSNumberFormatter, so let’s create the dateFormatter and currencyFormatter constants using custom getters as you need them.

In the presentFetchedOrders() method, you want to iterate on the orders in the response from the interactor. For each order, you’ll convert the date from NSDate to String, and total from NSDecimalNumber to String. You should also format the name to be a concatenation of the first name, a space, and last name.

Next, create a DisplayedOrder and append it to the displayedOrders array.

The other thing you want to do in the presentFetchedOrders() method is to create the ListOrders_FetchOrders_ViewModel with the displayedOrders array. Finally, you can simply invoke the displayFetchedOrders() method with the view model on the output.

Recap

Here’s what you accomplished in this post.

  • First, you isolated the dependency by creating the ListOrdersPresenterOutputSpy.
  • Next, you define the ListOrders_FetchOrders_ViewModel to be an array of DisplayedOrder to pass type-agnostic fetched orders to the view controller for display. The DisplayedOrder struct contains just Strings ready to be used by UILabels.
  • You wrote the testPresentFetchedOrdersShouldFormatFetchedOrdersForDisplay() test to make sure fetched orders are properly formatted.
  • You also wrote the testPresentFetchedOrdersShouldAskViewControllerToDisplayFetchedOrders() test to make sure the displayFetchedOrders() method is invoked on the output.
  • In the presentFetchedOrders() method implementation, you use the NSDateFormatter and NSNumberFormatter to format the order date and total, respectively. The result is an array of DisplayedOrder consisting of only strings suitable for display using UILabels.

In the process, you added the displayFetchedOrders() method in ListOrdersViewController. But it’s currently empty. This means fetched orders are formatted for display but not actually being displayed. Let’s tackle this in the next post.

You can find the full source code with tests at GitHub.

Raymond
I've been developing in iOS since the iPhone debuted, jumped on Swift when it was announced. Writing well-tested apps with a clean architecture has been my goal.

Leave a Comment

Your email address will not be published. Required fields are marked *