Test download of pdf with rspec and pdfkit

10,104

Solution 1

Not sure why you're getting that failure but you could instead test the response headers:

response_headers["Content-Type"].should == "application/pdf"
response_headers["Content-Disposition"].should == "attachment; filename=\"<invoice_name>.pdf\""

You asked for advice regarding better test coverage. I thought I'd recommend this: https://www.destroyallsoftware.com/screencasts. These screencasts have had a huge impact on my understanding of test-driven development -- highly recommended!

Solution 2

I recommend using the pdf-inspector gem for writing specs for PDF related Rails actions.

Here's an exemplary spec (which assumes the Rails #report action writes data about a Ticket model in the generated PDF):

describe 'GET /report.pdf' do
  it 'returns downloadable PDF with the ticket' do
    ticket = FactoryGirl.create :ticket

    get report_path, format: :pdf

    expect(response).to be_successful

    analysis = PDF::Inspector::Text.analyze response.body

    expect(analysis.strings).to include ticket.state
    expect(analysis.strings).to include ticket.title
  end
end
Share:
10,104
Dennis Hackethal
Author by

Dennis Hackethal

JavaScript, Clojure, Ruby

Updated on July 23, 2022

Comments

  • Dennis Hackethal
    Dennis Hackethal almost 2 years

    I am developing a rails 3.2 application with which users can download pdfs. I enjoy test driven development a lot using rspec and shoulda matchers, but I'm at a loss with this one.

    I have the following code inside my controller:

    def show_as_pdf
      @client = Client.find(params[:client_id])
      @invoice = @client.invoices.find(params[:id])
    
      PDFKit.configure do |config|
        config.default_options = {
          :footer_font_size => "6",
          :encoding => "UTF-8",
          :margin_top=>"1in",
          :margin_right=>"1in",
          :margin_bottom=>"1in",
          :margin_left=>"1in"
        }
      end
    
      pdf = PDFKit.new(render_to_string "invoices/pdf", layout: false)
      invoice_stylesheet_path = File.expand_path(File.dirname(__FILE__) + "/../assets/stylesheets/pdfs/invoices.css.scss")
      bootstrap_path = File.expand_path(File.dirname(__FILE__) + "../../../vendor/assets/stylesheets/bootstrap.min.css")
    
      pdf.stylesheets << invoice_stylesheet_path
      pdf.stylesheets << bootstrap_path
      send_data pdf.to_pdf, filename: "#{@invoice.created_at.strftime("%Y-%m-%d")}_#{@client.name.gsub(" ", "_")}_#{@client.company.gsub(" ", "_")}_#{@invoice.number.gsub(" ", "_")}", type: "application/pdf"
      return true
    end
    

    This is fairly simple code, all it does is configure my PDFKit and download the generated pdf. Now I want to test the whole thing, including:

    • Assignment of instance variables (easy, of course, and that works)
    • The sending of data, i.e. the rendering of the pdf => And this is where I'm stuck

    I have tried the following:

    controller.should_receive(:send_data)
    

    but that gives me

    Failure/Error: controller.should_receive(:send_data)
       (#<InvoicesController:0x007fd96fa3e580>).send_data(any args)
           expected: 1 time
           received: 0 times
    

    Does anyone know of a way to test that the pdf is actually downloaded/sent? Also, what more things do you see that should be tested for good test coverage? E.g., testing for the data type, i.e. application/pdf, would be nice.

    Thanks!

  • Dennis Hackethal
    Dennis Hackethal over 11 years
    Thanks, but unfortunately that gives me undefined method 'response_headers' - what am I doing wrong?
  • Dennis Hackethal
    Dennis Hackethal over 11 years
    Haha, I forgot to get :show_as_pdf :) That solves all problems. Thanks a lot!
  • Ian Vaughan
    Ian Vaughan over 9 years
    I'd like to test the download contents, but with selenium, which doesn't seem to work, anyone know more about that?
  • xhh
    xhh about 9 years
    using response.headers["Content-Type"] works for me
  • ChaosPredictor
    ChaosPredictor almost 8 years
    It works for me with rails 4 / rspec 3.5.0 but not for rails 5. any idea?
  • phil pirozhkov
    phil pirozhkov over 7 years
    Note: this spec should be a request spec, not controller spec, the latter stubs render with an empty string.