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

Rails 超お手軽な画像アップローダー CarrierWave の使い方 | Workabroad.jpRails 超お手軽な画像アップローダー CarrierWave の使い方 | Workabroad.jpはてなブックマーク - 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"

こんなフォーム。

コントローラ

 1class UsersController < ApplicationController
 2
 3  def edit
 4  end
 5
 6  def update
 7    @user = User.find(params[:id])
 8    if @user.update(user_params)
 9      redirect_to edit_user_url(@user)
10    else
11      render :edit
12    end
13  end
14
15private
16
17  def user_params
18    params.require(:user).permit(:name, :image)
19  end
20end

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

テスト要画像の用意

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

コントローラのテスト

 1RSpec.describe UsersController, :type => :controller do
 2  describe "PUT update" do
 3    before do
 4      # テストユーザーでログインしておく
 5      @user = login_user
 6
 7      # 先ほどのファイル
 8      @file = fixture_file_upload("sample.png", "image/png", true)
 9    end
10
11    it "アップロードが正常に終わり、@user.image?がtrueを返す" do
12      put 'update', id: @user.id, user: {
13        id: @user.id, name: "あたらしいなまえ", image: @file
14      }
15      expect(@user).to be_image
16    end
17  end
18end

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

最後のこの部分

1expect(@user).to be_image

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

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

fixture_file_upload

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

 1# actionpack-4.1.0/lib/action_dispatch/testing/test_process.rb
 2module ActionDispatch
 3  module TestProcess
 4    def fixture_file_upload(path, mime_type = nil, binary = false)
 5      if self.class.respond_to?(:fixture_path) && self.class.fixture_path
 6        path = File.join(self.class.fixture_path, path)
 7      end
 8      Rack::Test::UploadedFile.new(path, mime_type, binary)
 9    end
10  end
11end