PIYO - Tech & Life -

RSpecでファイルアップロードのテスト

これを参考にしてCarrierWaveを使った画像アップロード機能を実装してみた。

Rails 超お手軽な画像アップローダー CarrierWave の使い方 | Workabroad.jp

↑のサイトではCapybaraを使ったインテグレーションテストの書き方は載っているけど、コントローラのテスト方法が載っていなかったのでちょっとだけ詰まった。

言い換えると、今回はファイルを添付してアップロードするフォームのテストを書くというのと同義。

サンプル

フォーム

Userモデルには名前とプロフィール画像があって、更新フォームで名前とか画像をセットできることにする。

= form_for(@user, html:{ method: :put, role: "form" }) do |f|
  .form-group
    = f.label :image, "プロフィール画像"
    = f.file_field :image, class:"form-control"
  .form-group
    = f.label :name, "名前"
    = f.text_field :name, :autofocus => true, class:"form-control"

こんなフォーム。

コントローラ

class UsersController < ApplicationController

  def edit
  end

  def update
    @user = User.find(params[:id])
    if @user.update(user_params)
      redirect_to edit_user_url(@user)
    else
      render :edit
    end
  end

private

  def user_params
    params.require(:user).permit(:name, :image)
  end
end

updateメソッドがあって、nameimageを更新できるようにpermitしている。

テスト要画像の用意

#{Rails.root}/spec/fixturesにテスト要のファイルを配置する。今回は画像ファイルなので、適当なファイルをsample.pngという名前で置いておくことにした。

コントローラのテスト

RSpec.describe UsersController, :type => :controller do
  describe "PUT update" do
    before do
      # テストユーザーでログインしておく
      @user = login_user

      # 先ほどのファイル
      @file = fixture_file_upload("sample.png", "image/png", true)
    end

    it "アップロードが正常に終わり、@user.image?がtrueを返す" do
      put 'update', id: @user.id, user: {
        id: @user.id, name: "あたらしいなまえ", image: @file
      }
      expect(@user).to be_image
    end
  end
end

fixture_file_uploadfixturesディレクトリに置いたファイルをロードできる。そして、コントローラのupdateへのPUTリクエストをファイルを含めて呼び出すことでアップロードの完了を確認している。

最後のこの部分

expect(@user).to be_image

が直感的ではないけど、RSpecのルールに従って書くとそうなるというだけで、↓のように書いても問題はない。

expect(@user.image?).to eq(true)

fixture_file_upload

ちなみにfixture_file_uploadっていうメソッドはActionPackの中で定義されていて、見たところファイルアップロードのテストに使うRack::Test::UploadedFileを少し便利に使うために見える。

# actionpack-4.1.0/lib/action_dispatch/testing/test_process.rb
module ActionDispatch
  module TestProcess
    def fixture_file_upload(path, mime_type = nil, binary = false)
      if self.class.respond_to?(:fixture_path) && self.class.fixture_path
        path = File.join(self.class.fixture_path, path)
      end
      Rack::Test::UploadedFile.new(path, mime_type, binary)
    end
  end
end