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

f:id:ryoutaku_jo:20190821230008p:plain

【結論】

・リンクをクリックして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

《今日の学習進捗(3年以内に10000時間に向けて)》

CSV出力のテストが正常に実行されないということで調査を行った。
エラー箇所を確認すると、ファイルのダウンロードが実行されていない状態だった。
本機能は、JSのonChangeによって、セレクトタグ選択後にフォームを送信する設計になっているが、js: tureが設定されていなかった。

js: ture設定後、ファイルのダウンロードはテスト環境でも出来る様になったが、今度はレスポンドヘッダが見れなくなった。
調べてみると、導入しているドライバ(headless chrome)がレスポンスヘッダを見るAPIを提供していないようだった。
その為、実際にダウンロードしたファイルの中身をチェックする方法に変更した。

RSpecでのテストは、周辺環境の設定や仕様が複雑なので、少し凝ったテストをしようとすると非常に苦労する場面が多いと感じる。
それらをキャッチアップすることも重要だが、設計段階でテストがしやすい様に工夫することも必要だと考えている。

学習開始からの期間 :257日
今日までの合計時間:2432h
一日あたりの平均学習時間:9.5h
今日までに到達すべき目標時間:2347h
目標との解離:85h
「10,000時間」まで、

残り・・・「7568時間!」