Benito Serna Tips and tools for Ruby on Rails developers

How to test a PDF download with capybara

Do you want to test a PDF download with capybara, but don’t know how to do it?

Here I will how you can doing it are using the default driver (not the javascript driver).

What do you want to test?

The first thing that you are going to need, is knowing what do you want to test, here I will show you how you can test…

… but maybe some of them are not important for your specific use case, so you can change the code to fit your needs.

How to test if the content is right

You can compare the page.body with the content of the file that you want to send.

For example if you are using carrierwave and have a Receipt record like this…

class Receipt < ApplicationRecord
  belongs_to :user
  mount_uploader :pdf, PdfUploader
end

You can send the pdf like this…

class ReceiptsController < ApplicationController
  def show
    receipt = Receipt.find(params[:id])

    send_data receipt.pdf.read,
      type: receipt.pdf.content_type,
      filename: "Receipt-#{receipt.id}.pdf",
      disposition: "inline"
  end
end

You can write a test like this…

RSpec.describe "User downloads receipt pdf" do
  scenario "download from the receipts page" do
    user = create :user

    create :receipt, user: user

    login_as user, scope: :user

    visit receipts_path

    click_on "Download"

    expect(page.body).to eq receipt.pdf.read
  end
end

How to test if the filename is right

To test the filename you can check the Content-Disposition header, because it has the form…

#{disposition}; filename=#{filename}
RSpec.describe "User downloads receipt pdf" do
  scenario "download from the receipts page" do
    user = create :user

    receipt = create :receipt, user: user

    login_as user, scope: :user

    visit receipts_path

    click_on "Download"

    expect(page.response_headers["Content-Disposition"]).to match "Receipt-#{receipt.id}.pdf"
  end
end

How to test if the content type is right

To test content_type you can check the Content-Type header, like this…

RSpec.describe "User downloads receipt pdf" do
  scenario "download from the receipts page" do
    user = create :user

    receipt = create :receipt, user: user

    login_as user, scope: :user

    visit receipts_path

    click_on "Download"

    expect(page.response_headers["Content-Type"]).to eq "application/pdf"
  end
end

How to test if the disposition is right

To test the disposition you also can check the Content-Disposition header, because it has the form…

#{disposition}; filename=#{filename}
RSpec.describe "User downloads receipt pdf" do
  scenario "download from the receipts page" do
    user = create :user

    receipt = create :receipt, user: user

    login_as user, scope: :user

    visit receipts_path

    click_on "Download"

    expect(page.response_headers["Content-Disposition"]).to match "inline"
  end
end

A helper to test all together

If you want to test the four things together (or some of them), you can write a helper function to express that you want a download with some characteristics. You can do something like this…

RSpec.describe "User downloads receipt pdf" do
  scenario "download from the receipts page" do
    user = create :user

    receipt = create :receipt, user: user

    login_as user, scope: :user

    visit receipts_path

    click_on "Download"

    expect_download(
      content: receipt.pdf.read,
      filename: "Receipt-#{receipt.id}.pdf",
      content_type: "application/pdf",
      disposition: "inline"
    )
  end

  def expect_download(content:, filename:, content_type:, disposition:)
    expect(page.response_headers['Content-Type']).to eq content_type
    expect(page.response_headers['Content-Disposition']).to eq "#{disposition}; filename=\"#{filename}\""
    expect(page.body).to eq content
  end
end

… and that’s all for now. I hope it helps =)

Related articles