Testing Business Logic in Interactor

When it comes to writing tests for your app, how do you know which methods you need to test? I answered this question in my last post. Today, you’ll learn how to write tests for the CreateOrderInteractor class – the interactor that contains our CleanStore app’s business logic.

Let’s dive right in.

Create the XCTestCase for the interactor

In Xcode, choose File -> New -> File, then select Test Case Class. Enter CreateOrderInteractorTests for class. Make sure the CleanStoreTests target is checked. Click Create. The CreateOrderInteractorTests.swift file is created.

In the last post, you already learned that you need to test the boundary methods. Here are the CreateOrderInteractor’s input and output protocols again:

The nice thing about Clean Swift is it tells you exactly what methods you need to test at the top of the file. For CreateOrderInteractor, you need to write tests for both the shippingMethods variable and formatExpirationDate() method. You may ask – how do you test a variable? The shippingMethods variable is just a getter method, so it is no different, as you’ll soon see.

Your first test is about to expire

Let’s write your first test to make sure the interactor’s formatExpirationDate() method invokes the presenter’s presentExpirationDate() method. Formatting data is a job for the presenter, after all. So you’ll want to make sure the presenter is handed the duty.

To get a reference to CreateOrderInteractor, you first need to create an instance of CreateOrderViewController – the subject under test. When you build and run the app, an instance is loaded from the storyboard for you by iOS. But when you run tests, that’s not the case. Let’s create your own instance from the storyboard.

Open your storyboard file. In the File Inspector of the Utilities Area on the right, make sure CleanStoreTests is checked. This is to make sure our test suite sees the CreateOrderInteractor.

Add the following method to CreateOrderInteractorTests.swift:

It’s pretty straight forward. You first grab the bundle, then the storyboard, and instantiate CreateOrderViewController. Finally, createOrderViewController.output is simply the interactor we want. The configurator in Clean Swift does all the hookup you need to set up the VIP cycle.

UPDATE

In fact, there is an easier way to create the subject under test. Thanks to Marq’s comment below. He pointed out I could just instantiate CreateOrderInteractor without going through all the storyboard stuff.

And he’s right. I didn’t really have to. I copied and pasted from my view controller tests from an existing project. Because that’s the way how it’s be done for the view controller, I didn’t think twice.

Now, I like Marq’s method much more. When you are testing the interactor, there shouldn’t be any UI or view controller involved. This reduces the testFormatExpirationDateShouldAskPresenterToFormatExpirationDate() method to just:

Let’s write some actual test code:

To invoke the formatExpirationDate() method, you need to pass it a CreateOrder_FormatExpirationDate_Request argument. You can easily create a CreateOrder_FormatExpirationDate_Request struct with today’s date.

Recruit a spy to work for you

What about the Then…? How can you make sure the presenter’s presentExpirationDate() method is called? To answer this question, we’ll first talk about mocking.

The output of CreateOrderInteractor is CreateOrderPresenter. But you don’t really want to create an instance of the presenter to test the interactor. That’s because you are only interested in the behavior of CreateOrderInteractor in CreateOrderInteractorTests. You don’t care about what CreateOrderPresenter does after the interactor finishes and passes the flow of control to the presenter.

So, what do you do? You’ll create a spy for the presenter. The spy secretly and silently records what happens to it when the test runs. (Now is a good time to review the different types of test doubles if you haven’t already.)

Oh wait, do I need to import some sort of testing or mocking framework first? No, no, no. Stop! I can see you firing up GitHub and opening the Podfile. Writing a spy, stub, or mock is so easy that you don’t need to introduce more dependency.

Here is how you write a spy:

That’s it. It is very simple, right?

The interactor output conforms to the CreateOrderInteractorOutput protocol, so your spy should do the same and implement the presentExpirationDate() method. When the method is invoked, you just need to set presentExpirationDateCalled to true. Later, when you want to verify the invocation did indeed happen, you check to see if presentExpirationDateCalled is true or false.

Here is the assertion:

Before you run the test, you also need to set the CreateOrderInteractor’s output to your new spy in place of the real presenter. Like so:

Your test case should now look like:

Ship today if you create an order now

There is another method in the CreateOrderInteractorInput protocol. You can treat the shippingMethods variable as a getter method. So you’ll want to test that.

When the view controller asks for the output’s shippingMethods, the flow of control returns back to the view controller immediately. The shippingMethods variable serves as a shortcut to the VIP cycle.

The test is pretty simple:

The setup is the same. You simply assert the returned shipping methods equal to the available shipping methods.

Let’s do some refactoring to remove the duplicate setup code and call it in the setUp() method. Here is your complete CreateOrderInteractorTests test case. Hit ⌘-U to make sure it passes.

Congratulations, you’ve come a long way!

You’ve learned:

  • Break up a codebase into different Clean Swift components
  • Use the VIP cycle to extract your business and presentation logic
  • Write a test. Yay!

Next up is the presenter. In my next post in this testing series, you’ll learn how to test the CreateOrderPresenter in our CleanStore app. You’ll find all the code and test in the GitHub repo.

So long. Until next week.

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.

23 Comments

  1. Hi Raymond,

    I am enjoying the series and can’t wait for more, your site is helping with creating an iOS app. A quick question though, What is the purpose of creating a ViewController to test the Interactor? Couldn’t u just instantiate it directly and remove any references to the UI/ViewController?

  2. When I followed your ShippingMethods test case (in Objective-C), using this:

    It fails. The XCTAssertEqual expected scalar types and won’t work with objects. Is this a Obj-C vs Swift difference?

    To make it succeed, I changed it to:

    1. Hi Darren,

      This is one of those Objective-C things as pointed out on Stack Overflow. You can use one of the following in your assertions:

  3. Some questions on Workers…

    1. Do you setup one worker class per function, or do you group together related functions into a single worker?

    2. Do you initialize another worker every time the interactor needs one?. Or do you reuse the same worker when needed by the same interactor?

    3. If the worker is doing the heavy interfacing to other systems (ie: database, network, etc), does it make sense to create a prototype for each worker and create Test Doubles for the workers in your Tests?

    1. I’ll get to workers in some future posts. But I want to answer your questions now. Here is a simplified version of my answers at the conceptual level:

      1. It depends. I’ve written a single class to talk to a simple API that has 3 calls. On the other hand, I’ve also broken up a API with 15 calls into 4 workers. Each worker handles a different aspect of the API. So how you break up the actual work done by the workers highly depends on the complexity of your business logic.

      2. I usually instantiate a new worker at the interactor level. So, when the same interactor needs to use the same worker, it’ll be reused. But a different interactor should instantiate its own worker. It shouldn’t reuse a worker from another interactor in another scene, nor from another worker used in the same interactor, nor from the presenter in the same scene.

      3. When I test the interactor, I create a test double to stand in for the worker. When I test the worker itself, I create a test double for the API endpoint. Also, if your app only consumes the API (i.e. send GET requests to receive response), you don’t really care about the API spy’s behavior. So you would just use a stub instead of a spy. But if your app also modifies data over the API (i.e. send POST requests with some arguments to change something in a DB), you’ll want to use a spy or mock to make sure you are calling the API with the correct parameters.

      1. Thanks for the insight. I’ve known for a decade the benefits of Unit Tests and have tried on a number of occasions to implement them, but they practice never really “clicked”. Too much effort vs limited return, fragile tests, decided WHAT to test, etc, especially with iOS development.

        With your Clean Swift architecture, everything has just “clicked”. The unit tests have flowed out of the keyboard and I’m excited about an architecture in a way that I haven’t in a long time.

        (WARNING: More questions will be following, I’m sure!)

        1. Thank you, Darren. That means a lot to me. Those were my exact same thoughts 2 years ago as well! I’ve also enjoyed that “clicked” moment you mentioned. That’s why I wrote this blog. Just happy to be able to help other iOS developers who want to start writing tests.

      2. When you create a Test Double to stand in for a Worker for the Integrator, how do you interject the Worker test double into the Integrator? Does it make sense to create a protocol for the Worker?

        In my current test app (tvOS), I just created a derived class from the Worker and over-rode the method(s) that I needed, doing what I needed without a protocol.

        1. There are two common scenarios when it comes to testing an interactor with workers.

          When you are testing the interactor, the fact that the interactor uses a worker to perform the business logic is considered internal implementation details. So you probably don’t even want to have worker in your interactor tests. You should be fine by just mocking the presenter output. Remember, you don’t want to test private methods or internal implementation. Otherwise, your tests can become fragile. Testing the boundary methods is sufficient.

          However, there are some instances you may want to mock the worker inside the interactor, especially if the worker is acting as some sort of an output. Let me give you an example. Let’s assume you are fetching IAP products using an IAPWorker. It is asynchronous and the results don’t come back until later. You can’t pass the flow of control to the presenter until you get the results. It makes sense to mock the IAPWorker. Then, in your tests, you just need to make sure the interactor invokes the expected method with expected arguments on the IAPWorker. If you create your worker in your interactor like let iapWorker = IAPWorker(), you can then just use setter dependency injection like interactor.iapWorker = IAPWorkerSpy() in your interactor tests.

          Note that I am using the term mock in the general sense as in standing in for the real worker object.

          1. I would be curious to see more about the worker concept as I am little confused by it’s purpose. The way that I interpreted it was more like a Service layer or Gateway (another boundary), which would retrieve objects (from a service, database, etc…) and then provide them to the interactor which would do something and pass the output back to the presenter. If the business logic is inside of the Workers, then what’s the purpose of the interactor? also where would code to a service or database then reside?

            thanks,

  4. I wasn’t quite sure the best post to add this comment to….this is the closest match I could find!

    So, I’ve got the concept, and it’s going well….very pleased with the results. I understand the flow within the VIP….and I understand calling the Router….BUT, how do you best handle the flow/routing between different VIP Scenes?

    Here’s my current example:

    I have a SignIn Scene that handles the user selecting Facebook/Twitter signUp, with a SignUp button at the bottom.
    My first reaction was to just put a method in the Interactor (called by the ViewController’s action method), that sends a message to the Presenter, which sends a message to the ViewController to call the Router.
    My biggest problem with this is that it puts Application Flow logic in the Scene. Should the Scene really handle navigation between scenes?

    I couldn’t find anything in your CleanStore repo that gave a clue on your thinking here…Thoughts?

    1. Darren,

      Routing is triggered by the user (tapping a button) in the user interface. In most cases, you don’t want to put application flow in the VIP cycle. Your view controller knows when and which scene to navigate to next. Your router knows how to navigate to the next scene.

      In your router:

      In your view controller:

      If you use segue in the storyboard and don’t need to trigger a segue programmatically. You don’t even have to do the above. Just let the storyboard handle it.

  5. Hi,

    In my application, I’m creating the UI in code, with no Storyboards or XIBs. Where and when do I call the configurator so that it doesn’t interfere with the unit tests? AwakeFromNib doesn’t work in this case.

    Also how do I insert code snippets to like in your blog post?

    1. Hi Pedro,

      My next update will make the templates work when you create your UI in code. For now, you can override the following in your view controllers:

      override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
      required init?(coder aDecoder: NSCoder)

  6. Raymond,

    I believe I followed your entire post here and understood what we were doing and why, but had one question for you.

    Why do we maintain a reference to the Interactor via its concrete implementation, instead of its Input protocol? If I understood, our tests should only be testing our Protocols as we’re looking to only test the boundary methods and variables.

    I believe we lose two things by maintaining a concrete reference:
    1) The ability to hotswap a new concrete implementation and reuse our already written tests
    2) We expose concrete helper functions and variables that shouldn’t be accessible by the tester itself

    If I haven’t made a mistake, I think

    // Subject under test
    var createOrderInteractor: CreateOrderInteractor!

    Should be changed to

    // Subject under test
    var createOrderInteractor: CreateOrderInteractorInput!

    That way we only maintain a reference through the Protocol. I’d be interested to hear your thoughts on the matter. Whether my suggestion is totally valid, but changes nothing in reality due to this sample project’s simplicity or I’m making a mistake somewhere.

    Thanks,
    Greg

    1. Hi Greg,

      Interesting observation! You do have a good, valid point.

      Since we’re testing CreateOrderInteractor in CreateOrderInteractorTests.swift, we tend to instantiate the test subject as it is, with all the properties and methods available for manipulation and observation. And yes, we’re testing only the boundary methods, so accessing them through the protocol is enough.

      However, having a concrete test subject allows us to write a specific test case to test a private method for the subject. Again, I’m not saying we should test all private methods. Normally, testing the boundary methods should be enough by itself as we don’t want to couple our tests to the implementation. But if there’s a particular private method that contains some business logic that we need to permutate all possible inputs, I wouldn’t object to having such a test in this test file.

      Lastly, it allows us to spy easily:

      So, while I can’t disagree with you, I still think it’s better to have a complete test subject to work with.

  7. Thank you. However I don’t see any good values of these test to be honest.
    Let’s say for example we have a new shipping method. So we should change both code in test and presenter (the same stuff!).

    In addition, we should make the presenter object nil in tearDown.

Leave a Comment

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