RSpecでファイルのダウンロードをテストする方法

【結論】

・リンクをクリックしてCSVファイルをダウンロードする機能の場合、response_headersでレスポンスヘッダの情報を見てテストが出来る

response_headersrack_testでは利用できるが、他のドライバ(headless chromeなど)ではresponse_headersが使えない場合がある

・その場合はダウンロードしたファイルを直接確認する必要がある(コードは本文参照)

【目次】

【本題】

レスポンスヘッダの情報を見る方法

send_fileでファイルダウンロードを行う機能をテストする場合、簡単な方法の一つはresponse_headersでレスポンスヘッダの情報を見る方法です。

例えば、リンクをクリックするとCSVファイルがダウンロードされる機能をテストする場合、click後に以下の様に実装します。

expect(page.response_headers['Content-Disposition']).to include('xxx.csv')

ダウンロードファイルを直接確認する方法

先ほどの方法は、ドライバにrack_testを利用している場合は有効ですが、それ以外のドライバでは利用できない場合があります。

例えば、JavaScriptの操作も含まれるので、ドライバにheadless chromeを指定した場合などは、レスポンスヘッダを見るAPIが用意されていない為、先ほどの方法が使えません。

その様な場合には、ダウンロードされたファイルを直接確認する必要があります。

1:DownloadHelperを用意する

まず、ダウンロードするファイルの保存場所の定義や、ファイルの有無を確認する為のメソッドを定義したDownloadHelperを作成します。

以下が、そのコードです。

module DownloadHelper
  TIMEOUT = 10
  PATH    = Rails.root.join("tmp/downloads")

  extend self

  def downloads
    Dir[PATH.join("*")]
  end

  def download
    downloads.first
  end

  def download_content
    wait_for_download
    File.read(download)
  end

  def wait_for_download
    Timeout.timeout(TIMEOUT) do
      sleep 0.1 until downloaded?
    end
  end

  def downloaded?
    !downloading? && downloads.any?
  end

  def downloading?
    downloads.grep(/\.crdownload$/).any?
  end

  def clear_downloads
    FileUtils.rm_f(downloads)
  end
end

downloads.rbなど分かりやすいファイル名を付けて、spec/support配下に格納します。

なお、supportディレクトリ配下にファイルを置く場合は、rails_helper.rbの以下のコードのコメントアウトを外す必要があります。

コメントアウトのままだと読み込まれません。

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

2:rails_helperにDownloadHelperの読み込み

DownloadHelperが作成できたら、その読み込みや、ファイル保存先の指定などをrails_helper.rbに記述します。

RSpec.configure do |config|
  # ...

  config.include DownloadHelper, type: :system, js: true
  config.before(:suite) { Dir.mkdir(DownloadHelper::PATH) unless Dir.exist?(DownloadHelper::PATH) }
  config.after(:example, type: :system, js: true) { clear_downloads }

  # Chrome mode
  config.before(:each, type: :system, js: true) do
    driven_by :selenium, using: :headless_chrome, screen_size: [1920, 1080]
    page.driver.browser.download_path = DownloadHelper::PATH
  end
end

上から順に以下の様なことを定義しています。

  • JavaScript操作が必要なシステムテストが実行される際、DownloadHelperの読み込み

  • テストが実行される一番はじめに、tmp/downloadsディレクトリの有無を確認+作成

  • 単一のテストexampleが完了後、ダウンロードしたファイルの削除(初期化)

  • JavaScript操作が必要なシステムテストが実行される際、ドライバにheadless_chromeを指定

  • JavaScript操作が必要なシステムテストが実行される際、ダウンロードファイルの保存先を指定

3:テストコードをダウンロードされたファイルを確認する内容に修正する

最後に、該当のテストに、読み込んだファイルをチェックするテストコードを記述します。

expect(download_content).to include('XXX')

なお、ファイルの中身ではなく、ファイル名でテストを実行したい場合は、DownloadHelperに以下の様にファイル名を取得するメソッドを定義して、それをテストで呼び出せば良いです。

  def download_file_name
    wait_for_download
    File.basename(download)
  end
expect(download_file_name).to include('XXX.csv')

参考情報

poltergeistからheadless chromeへ移行する時に気をつけること - メドピア開発者ブログ

Testing File Downloads with Capybara and ChromeDriver | Collective Idea

capybaraでファイルダウンロードをテストする - Qiita

Rspecでダウンロードされたかテストする方法 - Qiita

send_data でダウンロードするファイル名をテストする - kakakakakku blog