Notes to self

Testing PDF generation in Ruby

If you have ever generated PDFs in your Ruby apps you were probably also asking yourself how do I test that the PDF turns out the way I want?

In my opinion there are three kinds of test you can do to easily test your final PDF file. The first one is to parse/analyze the PDF file and check that your expected string is present:

class SampleTest < Minitest::Test
  def test_setting_issue_date_and_due_date
    params = default_document_params.merge(
      issue_date: '05/03/2016',
      due_date: '14/03/2016'
    )
    invoice = InvoicePrinter::Document.new(params)
    rendered_pdf = InvoicePrinter.render(document: invoice)
    pdf_analysis = PDF::Inspector::Text.analyze(rendered_pdf)
    assert_equal true, pdf_analysis.strings.include?('Issue date:')
    assert_equal true, pdf_analysis.strings.include?('Due date:')
  end
end

The above example utilizes pdf-inspector library from the creators of the awesome Prawn library to get us the array of strings present in the PDF. Then we just simply check that the content we expect to be there is present.

When the code generating the PDFs gets a bit more complicated, such as with many conditions on what to show or not and where, we can even test exactly what is being present together with the position of the elements appearing on the PDF.

PDF::Inspector::Text.analyze(rendered_pdf).strings returns the array of all elements starting with the most top element. This gives us one more thing to test; that our elements comes in the expected order. It’s not the best test of all times, but one that you can easily do.

First we need a test helper returning the same strings representation as the one coming from PDF::Inspector. For that we can extend our PDF class by a new method that will know how to return the strings representation of our PDF objects:

class PDFDocument
  # Expose the document as an array of attributes in order as their
  # appear on PDF
    def to_a
      strings = []
      strings << @labels[:name]
      strings << @document.number
...

Once we have this in place, making the test is easy:

class SampleTest < Minitest::Test
  def test_render_document
    invoice = InvoicePrinter::Document.new(default_document_params)
    rendered_pdf = InvoicePrinter.render(document: invoice)
    pdf_analysis = PDF::Inspector::Text.analyze(rendered_pdf)
    # Here we are getting the expected results from our test helper
    strings = InvoicePrinter::PDFDocument.new(document: invoice).to_a
    assert_equal strings, pdf_analysis.strings
  end
end

This will help us to test much more while still using Ruby.

The last test that is probably kind of obvious is to compare a pre-generated PDF files with the new generated ones. This is great for final checks, but if those tests fail it’s harder to find out where exactly our library broke. The main advantage here is that we get to test the exact positions of our elements. Combining all these kinds of test can assure a better quality of our PDF code generation without having to carefully study generated PDFs and worrying about edge cases.

For the curious the examples here are coming from my upcoming gem for creating invoices InvoicePrinter.

Work with me

I have some availability for contract work. I can be your fractional CTO, a Ruby on Rails engineer, or consultant. Write me at strzibny@strzibny.name.

RSS