15

What's the best way to test the following code using RSpec? And what should I be testing for? The show action opens a file and streams it. Also, if the action relies on a file existing somewhere, can I test that?

def show
  image_option = params[:image_option]
  respond_to do |format|
    format.js
    format.pdf {open_bmap_file("#{@bmap.bmap_pdf_file}", 'application/pdf', "#{@bmap.bmap_name}.pdf", "pdf", "pdf")}
    format.png {open_bmap_file("#{@bmap.bmap_png_file}", 'image/png', "#{@bmap.bmap_name}.png", "png", image_option)}
  end
end

private

def open_bmap_file(filename, application_type, send_filename, format, image_option = nil)
  filename = "app/assets/images/image_not_available_small.png" unless File.exist? filename
  path = Bmap.bmaps_pngs_path

case image_option
  when "image"
    filename = "#{@bmap.bmap_name}.png"
  when "large_thumbnail"
    filename = "#{@bmap.bmap_name}_large_thumb.png"
  when "thumbnail"
    filename = "#{@bmap.bmap_name}_thumb.png"
  when "pdf"
    filename = "#{@bmap.bmap_name}.pdf"
    path = Bmap.bmaps_pdfs_path
  else
    filename = "#{@bmap.bmap_name}.pdf"
    path = Bmap.bmaps_pdfs_path
end

begin
  File.open(path + filename, 'rb') do |f|
    send_data f.read, :disposition => image_option == "pdf" ? 'attachment' : 'inline', :type => application_type, :filename => send_filename
  end
rescue
  flash[:error] = 'File not found.'
  redirect_to root_url
end
Bryan Ash
  • 4,385
  • 3
  • 41
  • 57
user1146369
  • 217
  • 1
  • 2
  • 6

2 Answers2

33

I needed to test send_data in a controller action that downloads a csv file, and I did it in the following way.

controller:

def index
  respond_to do |format|
    format.csv do
      send_data(Model.generate_csv,
                type: 'text/csv; charset=utf-8; header=present',
                filename: "report.csv",
                disposition: 'attachment')
    end
  end
end

(rspec 2 solution) controller_spec:

context "when format is csv" do
  let(:csv_string)  { Model.generate_csv }
  let(:csv_options) { {filename: "report.csv", disposition: 'attachment', type: 'text/csv; charset=utf-8; header=present'} }

  it "should return a csv attachment" do
    @controller.should_receive(:send_data).with(csv_string, csv_options).
      and_return { @controller.render nothing: true } # to prevent a 'missing template' error

    get :index, format: :csv
  end
end

(rspec 3 solution) controller_spec:

context "when format is csv" do
  let(:csv_string)  { Model.generate_csv }
  let(:csv_options) { {filename: "report.csv", disposition: 'attachment', type: 'text/csv; charset=utf-8; header=present'} }

  it "should return a csv attachment" do
    expect(@controller).to receive(:send_data).with(csv_string, csv_options) {
      @controller.render nothing: true # to prevent a 'missing template' error
    }

    get :index, format: :csv
  end
end
Shevaun
  • 1,208
  • 12
  • 19
  • 1
    This was a nice solution but doesn't seem to work in with Rspec 3 – Jonathon Horsman Jun 23 '14 at 16:37
  • Jonathon Horsman - I added an rspec 3 solution, the syntax is slightly different – Shevaun Jun 24 '14 at 03:48
  • 3
    I do not recommend this. Mocking code you don't own is generally not a good idea - among others, it is explicitly warned against in the original OOPSLA mocking paper. If the API for `send_data` changes (say, with a Rails upgrade), you won't know that your code is broken. – Xavier Shay Jul 05 '14 at 17:24
  • @XavierShay Then how should we test this case? Can you share your thought or code you are aware of? – Arup Rakshit Oct 04 '17 at 18:07
  • 2
    For me it didn't work to `render nothing: true`, it still kept throwing a `"Missing template"` error. I solved it with calling the original: `expect(@controller).to receive(:send_data).with(csv_string, csv_options).and_call_original`. This doesn't feel like as clean of a solution but I don't think it does any harm in controller specs to actually send the response with the filename – user2490003 Mar 08 '19 at 17:46
3

It's helped me

stub(controller).send_data { controller.render nothing: true }
araslanov_e
  • 309
  • 2
  • 5