Clean Swift TDD Part 2 – Interactor

In my TDD part 1 post, you wrote a test to make sure ListOrdersViewController invoke the fetchOrders() method of the ListOrdersInteractor. Now, it’s time to make the interactor does something with the request. You’ll also see exactly how to delegate the business logic of fetching orders from the interactor to the worker.

Prepare the OrdersWorker for TDD

With a little foresight, we want to create an OrdersWorker to perform the following CRUD operations:

  • fetchOrders() lists the fetched orders.
  • fetchOrder() shows a selected order.
  • createOrder() creates a new order.
  • updateOrder() updates an existing order.
  • deleteOrder() deletes an order.

Also, the orders may be stored in Core Data or over the network. So the results may not be immediately available. So, let’s make these operations asynchronous and return the result in a completion block handler.

Later, you can use this same OrdersWorker to create a new order for the CreateOrder scene. But for now, let’s make it work for the ListOrder scene first. So, let’s just implement the fetchOrders() method. In TDD, you don’t want to think too far ahead.

Isolate the dependencies

Similar to TDD – Part 1, the output of ListOrdersInteractor is a type that conforms to the ListOrdersInteractorOutput protocol. It is a dependency to the interactor, so let’s mock it out in ListOrdersInteractorTests.swift.

After the interactor fetches some orders, it passes the orders to the presenter by calling the presentFetchedOrders() method with a ListOrders_FetchOrders_Response object containing the orders. So, you want to spy on the presentFetchedOrders() method.

Let’s create the ListOrders_FetchOrders_Response model to contain the orders result too:

That’s all the mocking you have to do in the VIP cycle.

But there is one more dependency in ListOrdersInteractor.

Since we want to delegate the order fetching to the OrdersWorker, ListOrdersInteractor needs a reference to the worker. The worker is external to the interactor.

This OrdersWorker isn’t an output of the interactor, and doesn’t explicitly participate in the VIP cycle. But it is an integral part of fetching orders. If OrdersWorker encounters an error or doesn’t return any results, the ListOrdersInteractor doesn’t know how to move forward. So, the ListOrdersInteractor depends on the OrdersWorker.

Let’s mock it out as well:

Specifically, you want to mock out the fetchOrders() method and make it return an array of Order objects in the completionHandler. You can just make it return an empty array for now. You’ll also want to remember the method was indeed invoked with fetchOrdersCalled.

The OrdersWorker doesn’t exist yet. So, create it with just the fetchOrders() method. Keeping the TDD spirit, the simplest implementation is just to return an empty array of orders.

You also need to create the actual Order model. Just leave it empty for now. You can fill out its attributes later when it becomes necessary.

Okay, the project should compile now. Let’s move on to the main course.

Write the test first

In the ListOrdersInteractor, you want to delegate the order fetching to the OrdersWorker. Let’s write the testFetchOrdersShouldAskOrdersWorkerToFetchOrders() test to verify that.

First, you use the ListOrdersInteractorOutputSpy and OrdersWorkerSpy instead of the real ListOrdersPresenter and OrdersWorker because they’re dependencies outside of the subject under test.

Next, you invoke fetchOrders() with a ListOrders_FetchOrders_Request object.

Lastly, you assert that the fetchOrders() method is invoked on OrdersWorkerSpy, and the presentFetchedOrders() method is invoked on ListOrdersInteractorOutputSpy.

Draw the boundary

Again, similar to when you tested the ListOrdersViewController, hook up the presentFetchedOrders() method in the boundary between ListOrdersInteractor and ListOrdersPresenter. Add the presentFetchedOrders() method to the ListOrdersInteractorOutput and ListOrdersPresenterInput protocols.

For now, just add an empty implementation of presentFetchedOrders() in ListOrdersPresenter. You just want to make sure the interactor asks the presenter to format the fetched orders. You don’t care how it does that. You’ll TDD that method when you write tests for ListOrdersPresenter.

Implement the logic

In the ListOrdersInteractor, you need to take care of two things:

  • Create an instance of OrdersWorker and invoke its fetchOrders() method.
  • When the fetched orders are returned in the block, invoke the presentFetchedOrders() method on the output to push the result forward in the VIP cycle.

Modify ListOrdersInteractor.swift to look like:

Don’t confuse ListOrdersInteractor’s synchronous fetchOrders() method with the OrdersWorker’s asynchronous fetchOrders() method. They just happen to have the same name.

Your test should now pass. All the code and test for this TDD example can be found on GitHub.

Recap

Let’s recap what you’ve done so far using TDD with Clean Swift.

  • You isolated the dependency on the ListOrdersPresenter component by creating the ListOrdersInteractorOutputSpy. You also isolated the dependency on the OrdersWorker by creating the OrdersWorkerSpy.
  • You wrote the testFetchOrdersShouldAskOrdersWorkerToFetchOrdersAndPresenterToFormatResult() test to make sure the task of fetching orders is delegated to the OrdersWorker. You also made sure the orders result is passed forward in the VIP cycle to the presenter for formatting.
  • You implemented a minimum fetchOrders() method in OrdersWorker to simply return an empty array of orders. When you test the OrdersWorker in the next post, you’ll tweak this method to return more meaningful results.
  • You implemented an empty presentFetchedOrders() method in ListOrdersPresenter. You’ll know what to do when you test the ListOrdersPresenter.
  • You finished the implementation for the ListOrdersInteractor’s fetchOrders() method by invoking fetchOrders() on the OrdersWorker. When the orders result is returned asynchronously in the completion handler, you invoke the presentFetchedOrders() method on the presenter for formatting needs.

You’ll learn how to test the OrdersWorker in the next post. Because orders can be stored in Core Data or over the network, you’ll provide a common API to interface with OrdersWorker. You’ll then implement a memory store so your tests can run fast, a Core Data store to persist orders locally, and an API store to store orders in a backend.

Exciting stuff. Stay tuned.

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.

6 Comments

  1. Hi Raymond,

    I have a question regarding the post “Clean Swift TDD Part 2 – Interactor”.
    Why don’t you use ListOrdersWorker? You created another worker class OrdersWorker to do the job.

    Very best,
    Ken

    1. Hi Ken,

      Code in ListOrdersWorker should only be used by ListOrdersInteractor, whereas code in OrdersWorker can be shared by interactors from other scenes.

  2. Hi Raymond:
    I follow the steps in this post with the newest template, and I found that my test for worker can not pass.
    I think it’s due to the sut won’t use the workerSpy since inside the implementation of the protocol method it would instantialize a new true worker:

    I can make the test pass by:
    1. comment out this statement, but it would fail in real circumstance;
    2. check to see whether the worker exists and only instantialize it if it doesn’t exist

    But the above 2 methods seem just to make the test pass without any benefits in real circumstance, any suggestion?

    Very best,
    Travis

    1. Hi Travis,

      You want to take the worker = SomeWorker() out because that’s just an example. Instead, you can instantiate it when it’s defined outside of any function, like let worker = SomeWorker().

      Now, in your interactor test, you can mock it out by creating a SomeWorkerSpy class and using setter dependency injection.

      Is that more clear?

      You can take a look at ListOrdersInteractor.swift and ListOrdersInteractorTests.swift in the updated CleanStore sample app on GitHub.

  3. Is there any way to check if its actually delegated properly.

    In the given example after calling following in interactor,

    self.presenter?.presentFetchedOrders(response: response)

    how to check if implementation of this method in presentation is being called or not

Leave a Comment

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