Category Archives: RSpec

Magic sprinkles for Capybara and PDF

One of the frequent things I end up doing these days is generating reports. When you create a start up called Report Hero, it comes with the territory. The two most common outputs that I’m generating are PDF, and word (yeah word, I’m sorry).

I’ve recently being going through some pain getting testing working with PDFs and capybara. It took me way to long to get to the answers, so I’m going to document what I ended up with.

First, it’s worth noting that at the time of writing, poltergeist doesn’t support downloading files:
https://github.com/teampoltergeist/poltergeist/issues/485
http://stackoverflow.com/questions/35585994/downloading-a-csv-with-capybara-poltergeist-phantomjs

This means that you’ll need to use a different driver, WebKit works well for me (https://github.com/thoughtbot/capybara-webkit). As mentioned on that github project page, by adding webkit you’ll probably need to make sure that you run your tests on CI with an xvfb server.

If you are on rails 3 (did I mention I was doing this work on a legacy project), the next thing to consider is that depending on your setup you’ll need to make it possible to have two threads running at the same time to help enable the wkhtmlwrapper gem (wicked pdf or pdfkit) to work. So in you’re test.rb environment you’ll need to set config.threadsafe!

Once you’ve got PDFs being downloaded, there a number of options for performing the tests around them. They essentially boil down to reading the content using something like PDF Reader, and then performing assertions on it.

Pivotal have a decent blog post talking about this: https://content.pivotal.io/blog/how-to-test-pdfs-with-capybara , and prawn have extracted a gem to help: https://github.com/prawnpdf/pdf-inspector . For what it’s worth in this project, I went with the prawn pdf-inspecter gem.

So, the final steps I ended up with are:

  • use the webkit driver so you can download the pdf (with appropriate CI settings)
  • (in rails 3) set config.threadsafe! so the pdf generation can happen
  • use pdf-inspector to extract content from the PDF and perform some assertions.

With this setup you can

Writing Beautiful RSpec Matchers

thoughtbot have created a really nice set of custom matchers for RSpec.  The Shoulda matchers make writing tests for rails models beautiful and clean.

Shoulda matchers make it possible to write specs of the form:

It’s easy to do this for yourself using the friendly matcher DSL that ships with RSpec. Let’s take a look at how.

In the simplest form, all you need to do is to call the define method, passing a name, and a block which in turn calls out to match. The simple case might be:

We’ve just defined a matcher :be_less_than which allows us to do the following:

If we use the describe auto subject feature of RSpec we can also write:

and

With that example we can see that we are getting close to the shoulda matcher syntax.  There’s still an extra level that helps make the shoulda matchers nice, chaining. Happily this is easy to do as well. The dsl provides a chain method.  The chain methods are called before the match, so you can use the chain calls to collect up additional information to validate.  Using our example above, we might want to add in an :and_greater_than chain.

This now will give us the ability to write:

Unfortunately the default descriptions don’t quite work as nice when there are chains in place. You’ll find that you’ll want to write a description in the matcher definition.

Unfortunately the description doesn’t automatically get used in the error messages when the matcher fails, so you’ll want to specify a failure_message_for_should. Basing it off the description is an ok starting point.

Happily RSpec does the right thing with the should not description, so with the details above the matcher will be good. There might be a case where you want a different failure_message_for_should_not, which can also be specified.

That covers how to write simple reusable RSpec matchers to make your test code look as beautiful as your production code. Go ahead and try it yourself.  I’d love to see comments or questions you might have on this.