The problem

Are you having trouble understanding your test suites? Do you find yourself scrolling up and down over a spec file to find where that method comes from? Are your specs difficult to read? If you use RSpec in your daily job I am pretty sure this will sound familiar to you. RSpec is a great testing gem with amazing community support, but in my opinion some features like before, let, subject (and shared features) sailing together in a ship waving the DRY flag can leak to poor test stories hiding the meaning behind them.

Using this FactoryBot‘s Order factory::

Let’s take a look to the following example:

Here we are testing the method Orders::Biller#invoice for regular and monthly subscription orders.

Although I have made this example specially for this post, it is based on “DRY” testing examples I find everyday. I could have written something even more confusing with shared_examples and stuff disseminated across the spec/support folder&sons, but I didn’t want to be so mean, lets keep it easy this time.

Focus on the example:

Ideally I would like to see at a glance what is being tested.

I don’t know if it is me, but I personally can’t say which method is being executed here. If you scroll up you will find it lost in a before block above in the clouds at the beginning of the describe "#invoice" block. It is also difficult to know which kind of order is being tested. Where does that user come from? What kind of order is that? To answer that last question you have at least a context coming to the rescue and telling you that we are dealing with a regular order but, what would happen if you have two or three examples, if not more, within that context? Shall you scroll again?  (╯°□°)╯︵ ┻━┻

While DRY is usually a great principle you should apply in your code base, when it comes to testing I prefer to sacrifice it in case it compromises the readability of the test.

Test phases

To write a good test story I use Thoughtbot’s four-phase test pattern. Each of your tests should have the following structure:

  1. Setup all the conditions required by your test
  2. Exercise the method being tested
  3. Verification of the results
  4. Teardown everything that should not persist after the test has finished

Having that in mind, I have refactored the example above as it follows:

Can you see now what it’s being tested? For me the readability has improved a lot. Now you can see which conditions does the test need to setup before it runs, you can also appreciate the method being tested and also what is being verified.

How do test phases apply to our example?

Lets take a look to the example we saw before refactoring, note I have added a comment with the phase name for clarity:

For the setup phase I have extracted everything needed to build a monthly subscription order out to build_monthly_subscription_order method, then the method being executed is there, clear, on its own exercise line and last you verify that everything went as expected. In this case, and in most cases, no teardown phase will be needed, as garbage collection will do its job after the show finishes to leave everything neat and pretty. In case your tests hit a database, you could wrap the teardown methods in an around block or use a gem like database-cleaner as in most cases this won’t be relevant for your test story’s readability.

I hope you enjoyed this post, if you did, please share it!

One Response