#はじめに
現在作成中のWebアプリケーションのテストフレームワークについて、前回作成した記事を考慮して、MinitestからRSpecへ移行しています。その際、Minitestではあまり意識していなかったテストの種類について、意識することが必要だと感じました。そのテストがUIのテストなのか、コントローラーのテストなのか、モデルのテストなのか、それによってテストの書き方が異なります。本記事ではRSpecのディレクトリ構成とテストの種類ごとのコードの書き方について簡単にまとめました。
参考にしました→使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
Everyday Rails - RSpecによるRailsテスト入門
##RSpecの主なディレクトリ
RSpecのテストの種類をSpecと呼びます。Specは通常、その目的を説明する正規のディレクトリ構造に配置されます。
【主なRSpecディレクトリ構成】
spec ー ー ー models
|
|ー requests
|
|ー features
|
|ー helpers
|
|ー mailers
|
|ー system
##主なSpecの役割について
ここでは、Model Spec、Request Spec、Feature Spec、System Specの役割について紹介します
###Model Spec
Model Specはバリデーション等のRailsのモデルのテストします。
下記のサンプルコードで確認していきます。
RSpec.describe User, type: :model do
describe 'ユーザー名' do
it 'ブランクの場合、無効な状態であること' do
user = User.new( name: ' ',
email: 'user@example.com',
password: 'password',
password_confirmation: 'password')
expect(user).to_not be_valid
end
it '長過ぎる場合、無効な状態であること' do
user = User.new( name: 'a' * 51,
email: 'user@example.com',
password: 'password',
password_confirmation: 'password')
expect(user).to_not be_valid
end
end
end
一つ一つ内容を確認していきます。
・type: :model
でspecを指定します。ここではユーザーモデルをテストしています。
・describe
は期待する結果をグループ化しています。
「ユーザーモデルのテストをする」こととその中で「ユーザー名のテストをする」ことを宣言しています。
・User.new
ではユーザーを作成しています。
・it
はexampleというdescribe
より小さい単位でまとめています。
it
内に複数のエクスペクテーションを記載できます。しかし、途中で失敗した場合、その先は流れない為、基本的にit
一つに一つのエクスペクテーションが推奨されます。
・expect
ではエクスペクテーション(expectation、期待する内容)を記載しています。
expect(user).to_not be_valid
は「userが有効ではないか?」ということをテストしています。
英文のようにテストを記載できるのがRSpecの利点の一つですね。
to_not be_valid
の部分は**マッチャ(matcher)**と呼ばれ様々な種類があります。
その他のマッチャについてはこちらに記載があります。
###Request Spec
Request Specは公式ドキュメントよると、「ルーティングを含むフルスタックを介して、スタブ1なしで動作を駆動するような設計されています。」とのことです。要はサーバーサイドのテストということです。
Request Specではコントローラのレスポンスをテストする際、HTTPメソッド(get、post、delete等)が使用できます。
RSpec.describe "AccessToUsers", type: :request do
describe 'ログインしていないユーザーのアクセス制限' do
it 'ユーザーを削除する' do
user = User.create( name: 'kojiro',
email: 'user@example.com',
password: 'password',
password_confirmation: 'password')
expect {
delete user_path(user)
}.to change(User, :count).by(0)
end
end
end
ここではdelete
メソッドを使用し、ログインしていないユーザーがユーザーを削除しても反映されていないことをテストしています。
expect {delete user_path(user)}.to change(User, :count).by(0)
は「delete user_path(user)にアクセスした際、アクセス前後のユーザー数の差分が0人ということを予想する」となります。
###Feature Spec
Feature SpecはUIテストになります。
RSpec 3.7から同様の機能を持つ、System Specを使用することを公式ドキュメントで推奨しています。
###System Spec
System SpecはFeature Specと同様、主にUIのテストになります。ブラウザ上で実行するE2E(エンド・ツー・エンド)のテストを記載できます。こちらはフロントのテストですね。また、デフォルトでは、Chromeブラウザーを使用しテストを実行しています。
ブラウザの操作をシミュレートするためにCapybaraを使います。
どのような記載になるか、下記のサンプルコードで確認していきます。
RSpec.describe "UsersLogins", type: :system do
it '有効なログイン' do
user = User.create( name: 'kojiro',
email: 'user@example.com',
password: 'password',
password_confirmation: 'password')
visit login_path
fill_in 'メールアドレス', with: 'user@example.com'
fill_in 'パスワード', with: 'password'
click_button 'ログイン'
expect(current_path).to eq user_path(user)
end
end
一つ一つ内容を確認していきます。
・まずUser.create
でユーザーを作成し保存します。
・visit
、fill_in
、click_button
といったメソッドは、DSLとよばれるCapybaraのドメイン固有言語です。
1.visit
はページへの移動を表しています。visit login_path
でログインページへの移動をします。
2.fill_in
はテキストボックスへの文字の入力を表します。fill_in 'メールアドレス', with: 'user@example.com'
で'メールアドレス'という名前のテキストボックスに'user@example.com'を入力しています。
3.click_button
ボタンのクリックを表します。
その他DSLはこちらに記載があります。
・expect(current_path).to eq user_path(user)
では、「現在のパスがuser_path(user)(ユーザー詳細)と同じかどうか」をテストしています。
また、Systemスペックではテストが失敗した際、失敗した内容のエラーメッセージと共に、失敗した箇所のスクリーンショットを表示します。
試しに、エクスペクテーションのuser_path(user)
をlogin_path
に変更しテストを実行してみます。
すると下記の出力が出ました。
Failures:
1) UsersLogins 有効なログイン、ログアウト
Failure/Error: expect(current_path).to eq login_path
expected: "/login"
got: "/users/1"
(compared using ==)
[Screenshot]: /(プログラムのパス)/myapp/tmp/screenshots/failures_r_spec_example_groups_users_logins_有効なログイン、ログアウト_119.png
# ./spec/system/users_login_spec.rb:14:in `block (2 levels) in <top (required)>'
このようにスクリーンショットで、結果を期待したlogin_path
(ログイン画面)ではなく、user_path(user)
(ユーザー詳細画面)が表示されていることがわかります。
#まとめ
ちなみにRequest Specでは、Capybaraが使えなかったり、Feature SpecではHTTPメソッドが使えなかったりまします。今実装しようとしているテストが、サーバー側かフロント側か判断が必要になりそうです。