Rails 4.2 のコントローラーのテストをRails 5.0の機能テストに書き直す
Ruby on Railsではコントローラーのテストの書き方が、Rails 5.0で変わりました。
Railsのバージョンを上げるときに、コントローラーのテストを書き直すかどうか判断に迷うことがあります。
概ね機械的に書き換えられる部分がもあります。それを明示して判断の助けになることを期待します。
一度、rails-controller-testing gemを入れて、Rails 5.0へのアップグレードを完了してから、コントローラーのテストを書き直す手順をオススメします。
この記事が対象とするテスティングフレームワークは、RSpecではなくMinitestです。
テストの書き方が変わった理由
Rails 4.2までは、コントローラーのテストはメソッドの単体テストでした。
入力として、コントローラーのメソッドに引数を渡し、戻り値が期待通りか確認します。
例えば、次のようなテストコードです。
class ClassDictionariesControllerTest < ActionController::TestCase
test 'get the dictionary' do
response = get :show, params: { target_id: targets(:one).name, format: :csv }
assert_response :success
assert_equal ['klass', 'http://example.com/klass'], response.body.split("\t")
end
end
get :show, params: { target_id: targets(:one).name, format: :csv }
が入力です。
-
show
がアクションメソッド名 -
params
が引数
です。
assert_response :success
assert_equal ['klass', 'http://example.com/klass'], response.body.split("\t")
で、レスポンスコードとレスポンスボディが期待通りか確認します。
ここまでは良い感じの単体テストです。
Railsではコントローラーとビューがインスタンス変数とテンプレートを介して値をやりとりします。
次の項目もテストしていました。
- 宣言されたインスタンス変数
- どのテンプレートがレンダリングされたか
例えば、次のようなテストコードです。
test 'should get index' do
get targets_url, params: {}
assert_not_nil assigns(:targets_grid)
end
targets_grid
はインスタンス変数名を指しています。
assert_not_nil assigns(:targets_grid)
はインスタンス変数@targets_grid
に値が設定されていることを確認しています。
考えてみれば、このテストが少し実装に踏み込みすぎています。
例えば、リファクタリングのために、コントローラーとビューで使うインスタンス変数の名前を@targets_grid
から、ただの@grid
に変更すると、それでテストが壊れます。
あるいは、ビューからは@targets_grid
を参照しなくなったあとも、このアサーションとコントローラーのインスタンス変数だけ残ってしまうかもしれません。
Rails 5.0では、コントローラーのテスト範囲をもう少し広げられるようにしました。
機能テストと呼ばれています。
入力は次の項目です。
- HTTPメソッド
- URL
- パラメータ
出力はレスポンスです。
レスポンスには、
- レスポンスコード
- レスポンスボディ
- レスポンスヘッダー
などが含まれています。
その値が期待通りか確認します。
例えば、次のようなテストコードです。
class ClassDictionariesControllerTest < ActionDispatch::IntegrationTest
test 'get the dictionary' do
get target_class_dictionary_url(targets(:one)), params: { format: :csv }
assert_response :success
assert_equal ['klass', 'http://example.com/klass'], @response.body.split("\t")
end
end
見た目はあまりかわりません。意味は多少変わっています。
get target_class_dictionary_url(targets(:one)), params: { format: :csv }
が入力です。
-
get
がHTTPメソッド -
target_class_dictionary_url(targets(:one))
がURL - paramsが引数
です。
言われて見ればRailsにはルーティング機能があるので、HTTPメソッドとURLがあれば、コントローラーのメソッドは一意に定まります。
assert_response :success
assert_equal ['klass', 'http://example.com/klass'], @response.body.split("\t")
で、レスポンスコードとレスポンスボディが期待通りか確認します。
こちらもほとんど変わりません。
レスポンスが@rosponse
に自動的に格納されるようになりました。
書き換え手順
テストが継承する親クラスを変更する
例えば
class TargetsControllerTest < ActionController::TestCase
を
class TargetsControllerTest < ActionDispatch::IntegrationTest
に置き換えます。
Deviseを使っている場合は、さらに
include Devise::Test::ControllerHelpers
を
include Devise::Test::IntegrationHelpers
に置き換えます。
この時点でテストを実行すると、書き換えていないテストケースがエラーになります。
エラーが起きてるテストケースを修正していきます。
メソッド呼び出し
エラーが起きるのはテストケース内の、コントローラーのアクションメソッド呼び出し部分です。
これを直していきます。
この作業は機械的に行えます。
すべてのエラーがなくなりテストが通れば、機能テストへの移行は完了します。
アクションメソッド名をURLに変更する
例えば、TargetControllerのindexメソッドを置き換えたときは、ルーティングをtargets#names
で絞り込んでヘルパーメソッドを探します。
bin/rails routes |grep targets#index
を実行します。
targets GET /targets(.:format) targets#index
targetsにurlをつけたtargets_url
をURLヘルパーとして使います。
grepで絞り込んだ時は、次のようにヘルパー名が表示されないことがあります。
~ bin/rails routes |grep targets#update
PATCH /targets/:id(.:format) targets#update
PUT /targets/:id(.:format) targets#update
この場合は bin/rails routes -c targets
のようにコントローラーで絞り込んでルーティングを表示すると、ヘルパーメソッドを見つけやすいです。
~ bin/rails routes -c targets
Prefix Verb URI Pattern Controller#Action
names_targets GET /targets/names(.:format) targets#names
targets GET /targets(.:format) targets#index
POST /targets(.:format) targets#create
new_target GET /targets/new(.:format) targets#new
edit_target GET /targets/:id/edit(.:format) targets#edit
target GET /targets/:id(.:format) targets#show
PATCH /targets/:id(.:format) targets#update
PUT /targets/:id(.:format) targets#update
DELETE /targets/:id(.:format) targets#destroy
root GET / targets#index
~ bin/rails routes |grep targets#update
この場合はtarget
にurlをつけたtarget_url
をURLヘルパーとして使います。
URLヘルパーには単数形と複数形があることに注意してください。
例
new
get :new, params: {}
↓
get new_target_url, params: {}
create
post :create, params: { target: {...
↓
post targets_url, params: { target: {...
index
get :index, params: {}
↓
get targets_url, params: {}
show
get :show, params: { id: @target }
↓
get target_url(@target), params: { }
id
をパラメータで渡していた場合は、削除します。
edit
get :edit, params: { id: @target }
↓
get edit_target_url(@target), params: {}
id
をパラメータで渡していた場合は、削除します。
update
put :update, params: { id: @target, ...
↓
put target_url(@target), params: { ...
id
をパラメータで渡していた場合は、削除します。
destory
delete :destroy, params: { id: @target }
↓
delete target_url(@target), params: {}
id
をパラメータで渡していた場合は、削除します。
その他
変則的なルーティングも、ルーティングを絞り込めばヘルパーメソッドを見つけられます。
get :names, format: :json
↓
get names_targets_url, params: { format: :json }
アサーションの置き換え
rails-controller-testing gem の使用をやめるには、アサーションで使っている
- assigns
- assert_template
を、置き換える必要があります。
これはテストケース毎に何を確認するか判断する必要があります。
機械的に置き換えることができません。
例えば、次のようなような置き換えです。
assert_not_nil assigns(:targets_grid)
↓
assert_match @target.name, @response.body
前者はインスタンス変数が設定されていることを確認していました。
後者は設定されたインスタンス変数を使って、画面に表示されている値を確認しています。
一旦、機械的に機能テストへの移行してしまい、アサートの置き換えは段階的に進めていくのがよさそうです。
Discussion