Clean Swift TDD Part 5 – Complete the VIP Cycle

In my last post, you learned how to test your presenter and format data properly for display. We also introduced a new displayFetchedOrders() method in the view controller. The purpose of this method is to gather the fetched orders in the view model and set the contents in the IBOutlets.

But we left the displayFetchedOrders() method empty because we focused on testing the presenter. We needed to make sure we were formatting the order date to the format we want and combine first and last names.

Now, let’s finish up by test driving the displayFetchedOrders() method so we can actually see the displayed orders in the UI.

We’ll follow the same TDD steps that you have been accustomed to in the last few posts:

  1. Isolate the dependencies
  2. Write the test first
  3. Draw the boundary
  4. Implement the logic

Since the boundary is already established by IBOutlets and IBActions, step 3 isn’t necessary. That makes our lives even easier!

Display fetched orders in a table view

Conceptually, remember the invisible UI component that interacts with the view controller using IBOutlets and IBActions? After accepting user inputs, the UI, or view, asks the view controller to do some task by invoking an IBAction method. In return, the view controller asks the UI to display data to the user by setting some IBOutlet contents (e.g. the text=() setter method on an UILabel).

Isolate the dependencies

The UITableView in the ListOrders scene is similar to an IBOutlet. It encapsulates everything that needs to be done to display an order to the user. Therefore, the UITableView is an external dependency to the ListOrdersViewController.

Let’s mock out this dependency in your ListOrdersViewControllerTests:

The easiest way to display fetched orders is simply to reload the table view when there are new orders to display. So you want to spy on the reloadData() method to record the fact that it has been invoked.

If reloadData() is called, you can be assured that the orders are refreshed in the table view. This is taken care of by Apple’s UIKit. So you don’t need to test UITableView’s reloadData() method.

Write the test first

The ListOrdersViewControllerTests test is very straightforward.

First, you set up the TableViewSpy. Second, you create a ListOrders_FetchOrders_ViewModel object containing an array of orders to be displayed. For testing purpose, an array of just one DisplayedOrder is sufficient.

Then you just call the displayFetchedOrders() method. You finish the test by asserting that the TableViewSpy’s reloadData() method is invoked.

Implement the logic

Here’s the implementation of the displayFetchedOrders() method that makes the above test pass.

We use an UITableView to list the fetched orders. So how does UITableView work?

You are going to add some UITableViewDataSource and UITableViewDelegate methods in the view controller. And these methods will need access to the displayed orders.

In order to facilitate this, let’s use a private local displayedOrders variable to store the formatted orders you receive in the view model. When you need access to the orders later – the count and contents, you can just reference displayedOrders.

Don’t forget to invoke UITableView’s reloadData() method.

Test for table view section and row counts

I kept the table view very simple because my goal is to show you Clean Swift and TDD, not how to build a fancy looking table. The table view is designed to have only one section and the number of rows is equal to the number of orders to display.

Write the test first

Here’s the test for the number of sections:

You simply invoke the numberOfSectionsInTableView() method and assert it always returns 1.

Implement the logic

And you make the numberOfSectionsInTableView() method implementation return 1. No surprise here.

Write the test first

Testing the number of rows is similarly trivial:

You first create an array of DisplayedOrder for testing purpose. You then invoke the tableView:numberOfRowsInSection() method. You assert that the return value equals the number of orders in the array.

Implement the logic

And you guessed it. The implementation is equally trivial. Just make it return the number of orders in the displayedOrders array.

Test for table view cell configuration

Chances are you already know very well how to configure table view cells. But what do you assert?

Think back conceptually to the invisible UI component.

Write the test first

After the view controller receives the fetched, formatted, orders in the form of a view model from the presenter, it asks the table view to refresh. The table view then configures its cells by setting the label text. Therefore, you simply need to make sure the label text are set correctly. If they are, it means the text=() setter method is invoked with the right argument.

Implement the logic

The tableView:cellForRowAtIndexPath() method should be familiar to you already. First, you find the order to display in the cell for the given index path. You then attempt to dequeue an existing unallocated cell with a specific identifier. If no such cell, you’ll need to create one.

Now that you have a cell, you simply set the textLabel and detailTextLabel’s test property to the order date and total, respectively.

Recap

Congratulations! You’ve just fully implemented a feature with the Clean Swift architecture, following the VIP cycle, using test driven development.

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

You are now fully equipped to tackle any feature requirement like a senior iOS software craftsman will.

Want to refer to my previous posts on testing? Here’s all my posts so far.

Testing in the Clean Swift architecture:

Test driven development:

Mocking external dependencies:

Get the Clean Swift Xcode Templates

Subscribe below to get my Xcode templates and learn how to apply the VIP cycle to your projects, extract business and presentation logic into interactor and presenter, navigate to different scenes using multiple storyboards, and write fast, maintainable tests with confidence to make changes.

I promise I'll never send you spam. You can unsubscribe at any time.

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.

27 Comments

  1. VIP is an interesting architecture, thank you for sharing your implementation with us!
    I’m questioning the granularity of this architecture and where certain types of functionality should be implemented. For example in the CleanStore code for the phone number, validation that the user entered a phone number could take place in the VC itself or could be offloaded to a worker, or placed in a protocol or extension. Where in this architecture do you put things that are simple enough to be included in the VC, but yet still maintain the VIP architecture?

    1. Hi Rich,

      Thank you for the nice words and question.

      The validation for the phone number can be very simple. It can just make sure that it’s present (not nil or empty). So it may be tempting to just leave that little business logic in the view controller.

      However, I can easily think of stricter rules. It should also exclude letters and symbols. But it may allow ‘(‘, ‘)’, and ‘-‘. Should it always be 10 digits like in the U.S.? What about the ‘1’ in 1-800-xxx-xxxx. When you start to accept orders internationally, there’s a whole set of rules to follow. And you also need to make sure the changes you make to the validation logic doesn’t break for the original use case (not nil or empty).

      Business logic expands. If we take the shortcut now to get a prototype or 1.0 out the door, we’ll need to refactor the validation logic to the proper place in the future.

      So my preference is to stick with the VIP cycle. You just need to add the validatePhoneNumber() method in the protocols. You then write the logic in the interactor. Even better, the logic to validate the presence of some string can easily be reused for the name, email, username, address and so on. It makes a lot of sense to move that to a worker for reuse.

      If you think about it, an IBOutlet tokenized as phoneTextField doesn’t really mean it is a phone number accepting text field. The named token is just to give context to the developer when he writes the code. The text field is just a text field. It accepts text input and doesn’t care what the text represents. So it shouldn’t know anything about validating phone numbers.

      Hope I answered your question.

  2. Hey Raymond,

    I wanted to get your thoughts around something. I just finished watching Uncle Bob’s talks around Clean Architecture and after reading all of your articles, one thing I didn’t see in Clean Swift that was mentioned in CA is the concept of Entities (Core Business Objects). In CA, UB has two business layers, one called entities (at the center) and another around that for application logic called Interactors. In his talks, these entities contain logic specific to a business domain (similar to DDD). For example, in the Create Order example, there would be an Order class that would have some potential logic in it and the interactors would call the logic in that class and possibly any other “Core” classes. I just wanted to know your thoughts around this?

    1. Hi Marq,

      Uncle Bob describes the Clean Architecture for an enterprise application. Theoretically, an enterprise is a huge company that has some core business logic and many applications (web, mobile, internal, …).

      For example, a financial company in the stock exchange industry has the same business logic for margin trading. This same business logic should apply to the web app, the iOS app, and some internal app the traders use when you place an order on the phone.

      You want this business logic in one place only, possibly at another internal server. You don’t want to allow an iOS app to calculate the price on the phone. What if someone jailbreaks and reverse engineers their iOS app to always set the price to $1? So you want to implement this very important business logic in the Entities.

      In Clean Swift, there is no entity because the iPhone is a client device. Entities containing core business logic typically don’t exist on the iPhone. Instead, you connect to an API endpoint which runs the core business logic and return a response. Therefore, you should encapsulate all your API calls in a Worker.

      Does it make sense?

      1. I can see both sides I guess, yes it can apply to something larger like in an enterprise, where in really complex systems your “business rules” would probably sit in a rules engine with an API wrapped around it. However in studying CA and Onion Architecture, I never got the impression that it only applied to large enterprise apps, most of the examples, are small-medium sized applications. Most iOS apps once you move beyond utility and hello world are going to start accumulating a decent amount of business rules, would you not package these into the application?

  3. Hi Raymond. In your Clean-store example you are using three different kinds of stores. I just don’t understand how do you use the in memory store and the core data store at the same time?

    1. Hi Albert,

      I showed three data stores in the sample app to demonstrate how easy it is to switch between them. The memory store is especially useful for unit testing, as you can easily inject a memory store in place of a Core Data store or API.

  4. Hi Raymond

    Thanks for sharing Clean Swift.
    There’s one scenario I’m not sure Clean Swift can be implemented with. Let’s say I submit an Order to an API and I may or may not navigate away from that screen to do something else, so the submit request is still going on in the background.
    If I’m still on the Order screen I want a message to show the submit order result (success or fail). However if I’m not on the screen then I don’t need to see it.
    When the Worker has completed, we return back to the Interactor but what happens then?
    How do we know whether to call presentSubmitOrderResult() on the Presenter or not because we don’t know if the View is actually being displayed. It doesn’t make sense to call on the Presenter if the View is not displayed.

    1. Hi Ryan,

      I think you should still invoke the presenter which invokes the view controller. The VC should be the one who determines what to do with the success/failure. Think about it. The user navigating away from the view controller is a detail neither the interactor nor the presenter should know about. They just notify the next guy in the chain. The VC can decide to do something or nothing.

      Saving the work to send one or two extra messages doesn’t provide any meaningful benefit. The goal is to write clean code so that you can make changes much more easily.

  5. Hi Raymond, I was wondering if you were going to be bringing out the next part of this blog any time soon in regards to the saving of a new item when done is clicked.

    I’m also interesting in how you would go about it? Would you do the saving through the interactor via its workers? In that case do you then go through the presenter to then complete the cycle or would you have a break case to the transition back to the previous scene? Alternatively would you do the saving in the view controller and then just go through the router back to the previous scene?

    1. Hi Jake,

      I’ll definitely expand on the CleanStore app to show other aspects of this Clean Swift architecture. Stay tuned for future posts.

      As for your specific question, it depends on your use case:

      If you want to provide some visual feedback to the user ASAP, you’ll want to complete the VIP cycle and let the presenter to format this “in progress” visual feedback so the view controller can display it to the user. When the asynchronous save operation does finish, you can invoke the presenter again. This time though, you want to pass the “success/failure” response to the presenter, then the view controller.

      You can also choose just to wait for the asynchronous save operations to finish before your interactor ever invokes the presenter.

      It depends on your requirements.

  6. Hi Jake,

    Yes, the VIP cycle should be completed.

    When the user taps the Done/Save button, from the doneButtonTapped() method, I would ask the interactor to create the order. The interactor will then ask the worker to create the actual order, in Core Data or over the network, …etc. This is usually an asynchronous operation.

    When it succeeds or fails, the completion handler is called. The interactor will then pass the response to the presenter and ask it to format the response into a view model to pass back to the view controller.

    At that point, the view controller has the freedom to do anything. For instance, it can display a message to notify the user whether the order was created successfully. It can also navigate back to the ListOrders scene. And what not.

    Does that make sense?

  7. Hey Ray,

    I am really enjoying using Clean Swift. Now that we have completed the cycle I think about how to test the configurators. Have I missed a blog post or haven’t we gone so far (yet)? How far would you go with testing the configurator?

    Thanks,
    Martin

    1. Hi Martin,

      The configurator’s only job is simply to hook up the VIP components, and not for daily app development. You needn’t worry about testing them. I’ve carefully designed and tested it when turning Clean Swift into Xcode templates. I didn’t feel the need to test it.

  8. Hi Raymond!

    I’m trying to unit test the correct display of a custom UITableViewCell with special labels, and I’m a bit stuck about how should I access the additional parameters of it, so I cannot test the proper initialization of my cells.

    I’ve tried to downcast the result of cellForRow:atIndexPath into CustomTableViewCell class without success (all text properties are nil after downcasting).

    The view is properly mapped when I run the application, but I think I shouldn’t continue without knowing how to test this behaviour with unit testing.

    Am I missing something?

    1. Hi Lucas,

      If I understand correctly, you have a UITableViewCell subclass that contains some labels. And you want to make sure the labels display the desired text.

      First, it’s important to understand that the actual task of displaying pixels on the screen shouldn’t be tested. This is done by Apple. As long as you are calling `label.text = “desired text”‘, you are good.

      Now the problem is reduced to:

      • Constructing the desired text – performed by the interactor and tested there
      • Formatting the desired text – performed by the presenter and tested there
      • Setting the label text to be the desired text – performed by the view controller and tested there

      I hope that gives you some ideas.

      As for the error you get, that’s something better for Stack Overflow.

  9. Hi Raymond

    Thanks for sharing Clean Swift.

    Recently I am studying your CleanStore code and all your article in order to implement Clean Swift in my App. (Rebuild from the ground up, because of a massive view controller)

    In one of your article you said we should only test “boundary methods”. As far as I know base on your explanation, those are all input protocol methods of VIP cycle, plus the forth invisible UI component Input methods (View Controller).

    But as I followed your TDD development, you also test worker, and in your CleanStore code you also test the CoreDataStore GRUD operations. So right now I am a little confused about the meaning of the “boundary methods”… I know between worker and Interactor there is a boundary, but the boundary seems quite private, and I think if we test the Input of the Interactor, haven’t we already implicitly tested the worker as well?

    Forgive my poor English, hope you can understand my question.

    1. Hi Andres,

      Smart observations and questions!

      First, yes, you should only test the boundary methods. In the VIP cycle, these boundaries are well established by the input and output protocols. So you only need to test these methods. Any other methods in the V, I, and P shouldn’t be tested explicitly. They are already invoked by the boundary methods when you test those.

      Now, for the workers and data store classes in the CleanStore app, I didn’t establish an explicit boundary for these entities. The reason I didn’t is because they are quite simple (after all, it’s a demo app). I could have listed them in a protocol. Or, I could mark them with the public and private keywords (a.k.a. the @interface in Objective-C). However, the same principle still holds. You should only test methods of workers and data stores when they are exposed to the callers. The other private methods are called by the other public methods of the same class.

      Lastly, you asked whether the workers are already implicitly tested when you test the interactors. Here, you want to make sure you know the difference between unit testing vs integration testing.

      When you unit tests the interactor, you mock out the workers (they are dependencies) that are used by that interactor. You’re isolating the interactor by itself and testing it as an individual unit. Same for a worker, you mock out the interactor so you can isolate the worker and test it as a single unit. This is unit testing. You provide inputs to the interactor and observe the outputs from the interactor.

      In integration testing, you test the whole system or subsystems. This is when you don’t mock out the workers and want to see if the interactor will invoke the workers. You provide inputs to the view/view controller and observe the outputs from the view/view controller – after it has gone through the VIP cycle traversing V, I, P and any necessary workers and data stores.

      Does this clear up some confusions for you?

      1. I didn’t know unit testing vs integration testing, but right now I got it!
        So, when we test the Interactor, the truth is the Worker won’t be test at all! (Because we mock it out for the sake of “unit” test).

        I think I misunderstood your “boundary methods” before. I thought they are only those 3 boundaries between V, I and P (-V-I-P). But right now I know “boundary methods” means ALL “boundary methods” in the app architecture. So maybe there are indeed only 3 boundaries if you have a very simple app, but as the app grows, we may have more workers and data stores, there will be more boundaries. And those are the ones we need to test, am I right?

          1. Hi, Raymond, according to you and @Andres Wang’s discussion, if the app becomes very complex, there may be more protocols for comunication between workers, data and interactors? (not just InteractorInput and InteractorOutput?)

          2. Hi Travis,

            As your app grows in complexities, the number of components (and hence the number of boundaries), the number of protocols, and the number of methods in your protocols, will grow, so you’ll have more methods to test. That’s an universal truth regardless of what architecture you use. Even for massive view controllers. One way to tackle this is to break down large components into smaller, more manageable pieces. I would rather have more, shorter files than fewer, longer files.

  10. Hey Raymond. Great article. I’m just a bit confused about how to implement UITableViewDelegate and UITableViewDataSource with clean swift. Should the DataSource be implemented by the interactor? Or should it stay in the UITableViewViewController? Thanks 🙂

  11. Hi Uncle Raymond! 🙂

    I have a question about the worker class – and the way that the interactor sets up and injects the type of worker (store/API etc) …however what if I want to make the worker decide this source option. i.e. if local copy of older data exists in store, the worker class sends that back immediately, while an API version subsequently sends back another update of the API return via the same completion block… and refreshes what’s stored locally.

    What’s the best way to implement that? Should one worker manage the different store options – local / API etc?

    Thanks again for any help!

Leave a Comment

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