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
.
1 2 3 4 5 6 7 8 9 10 11 12 |
class ListOrdersInteractorOutputSpy: ListOrdersInteractorOutput { // MARK: Method call expectations var presentFetchedOrdersCalled = false // MARK: Spied methods func presentFetchedOrders(response: ListOrders_FetchOrders_Response) { presentFetchedOrdersCalled = true } } |
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:
1 2 3 4 5 |
struct ListOrders_FetchOrders_Response { var orders: [Order] } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class OrdersWorkerSpy: OrdersWorker { // MARK: Method call expectations var fetchOrdersCalled = false // MARK: Spied methods override func fetchOrders(completionHandler: (orders: [Order]) -> Void) { fetchOrdersCalled = true completionHandler(orders: []) } } |
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.
1 2 3 4 5 6 7 8 |
class OrdersWorker { func fetchOrders(completionHandler: (orders: [Order]) -> Void) { completionHandler(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.
1 2 3 4 |
struct Order { } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
func testFetchOrdersShouldAskOrdersWorkerToFetchOrdersAndPresenterToFormatResult() { // Given let listOrdersInteractorOutputSpy = ListOrdersInteractorOutputSpy() sut.output = listOrdersInteractorOutputSpy let ordersWorkerSpy = OrdersWorkerSpy() sut.ordersWorker = ordersWorkerSpy // When let request = ListOrders_FetchOrders_Request() sut.fetchOrders(request) // Then XCTAssert(ordersWorkerSpy.fetchOrdersCalled, "FetchOrders() should ask OrdersWorker to fetch orders") XCTAssert(listOrdersInteractorOutputSpy.presentFetchedOrdersCalled, "FetchOrders() should ask presenter to format orders result") } |
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.
1 2 3 4 5 6 7 8 9 10 |
protocol ListOrdersInteractorOutput { func presentFetchedOrders(response: ListOrders_FetchOrders_Response) } protocol ListOrdersPresenterInput { func presentFetchedOrders(response: ListOrders_FetchOrders_Response) } |
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
.
1 2 3 4 |
func presentFetchedOrders(response: ListOrders_FetchOrders_Response) { } |
Implement the logic
In the ListOrdersInteractor
, you need to take care of two things:
- Create an instance of
OrdersWorker
and invoke itsfetchOrders()
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class ListOrdersInteractor: ListOrdersInteractorInput { var output: ListOrdersInteractorOutput! var ordersWorker = OrdersWorker() // MARK: Business logic func fetchOrders(request: ListOrders_FetchOrders_Request) { ordersWorker.fetchOrders { (orders) -> Void in let response = ListOrders_FetchOrders_Response(orders: orders) self.output.presentFetchedOrders(response) } } } |
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 theListOrdersInteractorOutputSpy
. You also isolated the dependency on theOrdersWorker
by creating theOrdersWorkerSpy
. - You wrote the
testFetchOrdersShouldAskOrdersWorkerToFetchOrdersAndPresenterToFormatResult()
test to make sure the task of fetching orders is delegated to theOrdersWorker
. 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 inOrdersWorker
to simply return an empty array of orders. When you test theOrdersWorker
in the next post, you’ll tweak this method to return more meaningful results. - You implemented an empty
presentFetchedOrders()
method inListOrdersPresenter
. You’ll know what to do when you test theListOrdersPresenter
. - You finished the implementation for the
ListOrdersInteractor
’sfetchOrders()
method by invokingfetchOrders()
on theOrdersWorker
. When the orders result is returned asynchronously in the completion handler, you invoke thepresentFetchedOrders()
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.
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
Hi Ken,
Code in
ListOrdersWorker
should only be used byListOrdersInteractor
, whereas code inOrdersWorker
can be shared by interactors from other scenes.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
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 letworker = 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.
Yes, I got it!
Thank you for your prompt reply!
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