Controllerのテストを書く場合、before_filterに惑わされることが多々ありました。
そこで、before_filter用のメソッドのテストとController上のactionのテストを分離する方法です。
環境
- ruby: 2.0.0-p0
- rails: 4.0.0.rc1
- rspec: 2.13.1
事前準備
Anonymous Controllerを有効にしておきましょう。
config.infer_base_class_for_anonymous_controllers = true
実装とテスト
class UsersController < ApplicationController
before_action :load_user
def index
@name = @user.name
end
private
def load_user
@user = User.first || raise("User does not exist.")
end
end
require 'spec_helper'
describe UsersController do
describe "GET 'index'" do
before do
controller.stub(:load_user).and_return(true)
@user = User.create :name => "hoge"
controller.instance_variable_set(:@user, @user)
end
it "リクエストに成功する" do
get :index
response.should be_success
end
it "必要なデータが帰ってくる" do
get :index
expect(assigns[:name]).to eq("hoge")
end
end
describe :load_user do
controller do
def index
render :text => "ok!"
end
end
it "Userがない場合にerror" do
User.delete_all
expect{ get :index }.to raise_error
end
it "Userがある場合に'ok!'がレンダリングされる" do
@user = User.create :name => "hoge"
get :index
assigns[:user].should eq(@user)
response.body.should == "ok!"
end
end
end
ポイント
[describe "GET 'index'"]にてindexアクションのテストをし、[describe :load_user]でbefore_filterのテストをしています。
indexアクション
stubを使ってbefore_filterで呼び出される処理を上書きしています。
controller.stub(:load_user).and_return(true)
before_filterで設定していた内容を手動で与えます。
@user = User.create :name => "hoge"
controller.instance_variable_set(:@user, @user)
あとは、純粋にindexメソッドをテストします。
before_filter
Anonimous Controllerでダミーのコントローラを作成します。
controller do
def index
render :text => "ok!"
end
end
あとは、before_filterに引っかかってエラーやリダイレクトが発生するかやインスタンス変数に値が格納されているか等をテストします。
まとめ
この方法を使用すれば、複数の場所で使用されているbefore_filterのテストを一箇所にまとめることができるかなと思います。
修正1
skip_before_filterを呼ぶより、普通にstubを使ったほうがシンプルで安全なので修正しました。
before do
controller.class.skip_before_filter :load_user
end
after do
controller.class.before_filter :load_user
end
before do
controller.stub(:load_user).and_return(true)
end
修正2
instance_evalよりもinstance_variable_setを使うほうがいいですね。
インスタンス変数に設定している@userをテスト中に使用できる等の利点があります。
controller.instance_eval do
@user = User.create :name => "hoge"
end
@user = User.create :name => "hoge"
controller.instance_variable_set(:@user, @user)