One of my favorite developments from moving to interactors in my Rails apps has been the way it’s enabled me to clean up my controller tests. Before, my controller tests needed to know what combination of attributes would cause a failure when creating a new user, for example. If that changed, my tests broke.
Really, all a controller test should be looking at is what happens when the call is successful, and what happens when it fails. Do we load the page? Do we redirect? Do we display a flash message? That’s what really matters, and interactors make it easier to manage and test those scenarios without getting lost in the details of what makes it fail.
Testing the Controller
We know that the controller is going to hit our
CreateUser interactor, and whether or not it is successful determines what the user sees next. Rather than concerning ourselves with how to create success or failure we can instead stub
CreateUser.call and only test what the controller is actually responsible for.
An interactor returns an
Interactor::Context, so we start by building a double that will represent that response, mocking any methods that we’d call on the context. That will always be
success?, but could be anything else we might use to populate the view or determine what to do in the controller, like the user, account, or something else.
success variable is what will determine what happens at the controller we’ll set that up in with
let inside each of the contexts. Then, when the test is run, the response from calling
CreateUser.call will either be successful or not based on what we’ve set up at the top of the context.
Setting up the Controller
Now that the tests are written we have a pretty good idea of what the controller needs to look like, and it’s just as simple to understand as the tests.
Interactors let our controller just pass params to
CreateUser and not worrya bout anything else. The business logic is compartmentalized in the interactor (or in multiple organized interactors, we don’t even need to know at the ctonroller level). We don’t need to worry about validating the email, tracking any metrics, or doing anything else in the controller.
All that matters when deciding what to do next is checking if
result.success? is true or not. The controller is easy to understand, and the tests are equally easy to implement.
Obviously you could do something simliar by mocking
User.create and returning a user double with
valid? stubbed to true or false and achieve a similar result. I am not against this, but I belive that interactors are a superior approach to this, and to moving the manipulation of data to a single flow. I now do almost everything in interactors, even things that used to live in callbacks on the models.
When we decide to put our business logic in to interactors there are lots of benefits across the app. Already skinny controllers can get even skinnier, and their tests get even simpler to understand and create. The tests can stop worrying about how to make something fail and instead worry about what happens when it does.