RSpecでsend_fileの送信するファイルの内容をテストする方法(修正版)

コントロラーのRSpecを書いていて send_file の response が上手くテスト出来なかったので調べてみました。ただし調べたのは Rails 2.3.8 , Rspec Rails 1.3.3 です。

@irohirokiさんから ActionController::TestProcess#binary_content を使えば良いですとTweetがあったので変更しました。

http://www.pragprog.com/images/covers/190x228/achbd.jpg



下のようなコントロラーのメソッドが在ったとき

  def download
     ・・・
      send_file "ファイルのパス", :type => 'application/octet-stream'
     ・・・
  end

RSpecを素直に書くと

it "POST /download はファイルの内容を戻す" do
   post "download"
   response.body.should == IO.read("ファイルのパス")
end

のようになりますが、これでは以下のようなエラーになってしまいます。

'XxxxxController POST /download はファイルの内容を戻す' FAILED
expected: "・・省略・・",
     got: #<Proc:0x0000000102372000@/opt/local/lib/ruby/gems/1.8/gems/actionpack-2.3.8/lib/action_controller/streaming.rb:93> (using ==)

以下のように ActionController::TestProcess#binary_content を使うと簡単にテストできました

it "POST /download はファイルの内容を戻す" do
   post "download"
   response.binary_content.should == IO.read("ファイルのパス")
end

ここからは古い記事(binary_contentがやってる事もほぼ同じでした)

どうもsend_file は 無名関数 (Proc) を戻しているようです。ソース action_controller/streaming.rb を見ると

render :status => options[:status], :text => Proc.new { |response, output|
  logger.info "Streaming file #{path}" unless logger.nil?
  len = options[:buffer_size] || 4096
  File.open(path, 'rb') do |file|
    while buf = file.read(len)
      output.write(buf)
    end
  end
}

戻している無名関数はファイルの内容をoutput引数へ出力する関数です。そこでこの関数に文字列ストリーム(StingIO)を渡してあげればファイルの内容が取得出来そうです、以下のようなコードを書いたところ無事 sned_fileの出力がテスト出来ました :-)

require 'stringio'

def receive_from_send_file(proc)
  stream = StringIO.new('', 'w+')
  proc.call(nil, stream)
  stream.string
end

it "POST /download はファイルの内容を戻す" do
   post "download"
   receive_from_send_file(response.body).should == IO.read("ファイルのパス")
end