In addition to merely replacing expensive method calls, test doubles enable a different testing style where we validate the application’s behavior rather than its ending state. In most of the tests we’ve seen throughout the course, the test validates the result of computation: it’s testing whether something is true at the end of an action. When using doubles, however, we have the opportunity to test the process’s behavior during the test rather than the outcome.

Controller testing deprecation

Often, this kind of test makes sense given a relatively complex set of object interactions. We don’t exactly have that here, but we can use the controller as a reasonable stand-in. Some features of controller tests are deprecated in Rails 5. Still, we won’t touch any of the deprecated features, and we’ll test the controller as its own unit, without dependencies on any other code. We think using test doubles is still a potentially valid way of unit-testing logic in controllers.

Traditional controller tests

Traditional controller tests, the deprecated kind, often test whether the controller sets a particular instance variable to pass off to the view and gives it a value matching the incoming data. However, given how we’ve written the controller, the specific values are the workflow’s responsibility. The controller is acting as merely a conduit. Its job is to get the value from some data source and pass it on. Using the controller test to verify the workflow or view behavior leads to brittle tests.

Controllers responsibility

What is the controller’s responsibility? The controller is just a traffic cop. If we test that the controller sets an instance variable, we’re testing the conduit’s output. The conduit input is verified not by the variable’s state at the end of the controller called but by how value is obtained. In other words, the controller’s responsibility is to meet a contract on both ends. It’s responsible for setting a particular value to satisfy the view, and it’s responsible for calling some other place in the system to acquire data.

The create controller responsibility

Specifically, the controller calls and then calls create on the resulting action. Leave the responsibility of verifying that CreatesProject accurately creates a project for that object (tests we wrote when we added the CreatesProject functionality). All we need to do here is specify that the controller calls the appropriate methods.

Mock objects expectations

Enter mocks. Mock objects are used to set an expectation for the controller’s behavior during the user action. This will look similar to the failed test from the last section, but we’re going to emphasize a different part of the test:

Get hands-on with 1200+ tech skills courses.