テストを実装してみる⑴モデル編
前回の記事で、テストについて理解しテストを行うのに必要なgemをインストールできました。
そこで今日はテストを実際にやっていこうと思います。
前回の記事でも言いましたが、
テストにはモデルのテストとコントローラーのテストがあります。
モデルでは正しくデータを保存できてるかのテスト、
コントローラーでは指定したアクションが動いてるか・指定したビューが表示されるようになっているかのテスト
を行います。
モデルのテストを行う
今回は今作っているチャットアプリにおけるテストを行いたいと思います。
チャットアプリにおいてメッセージ送信がきちんと行われるかを確認するために、
メッセージ送信機能のモデルをテストをしましょう。
モデルのテストコードを書いてみよう
まずファイルを作成する
今回はメッセージ送信機能のモデルをテストするので、
メッセージモデルのファイルを作成をしていきます。
では、どこのディレクトリの、どのファイルを作成し、記述していくべきなのか?
これはrspecの規則を理解する必要があります。
[RSpecの規則]
RSpecによるテストコードが書かれたファイルのことを、specファイルと呼びます。
全てのspecファイルは、先ほどのrails g rspec:installコマンドで生成された「specディレクトリ」の中に格納しておきます。
モデルに関するテスト用ファイル→spec/models/以下に、
コントローラーに関するテスト用ファイル→spec/controllers/以下に
格納されます。
またspecファイル名は対応するクラス名_spec.rbという名前になります。
つまり、今回はメッセージモデルのテストを
spec/models/のディレクトリの中に「message_spec.rb」 という名前で作成し、
そこにテストコードを書いていけばオッケーですね。
基本となるコードを書く
まず基本となるコードがだいたい定型文であるので、
それを書きます。
あとで説明するので、まずコードをみてください。
[1行目] 1行目のrequire 'rails_helper'は、
rails_helper.rb内の記述を読み込むことで共通の設定を有効にしています。
この1行目の記述は、全てのspecファイルに書き込みます。
[3~7行目] 3, 4行目に連続してdescribeが登場しています。describeは、このようにネスト(入れ子状)にすることができます。ここでは「messageクラスにあるcreateメソッドをテストするまとまり」であることを示しています。このように、describeとdoの間にメソッド名を書く際は#をつけるのが慣習です。
基本コードがかけたので、ネストした中に実際のテスト処理を行うコードを書いていきましょう!
テストコードを書いていく
まず何をテストしなきゃいけないのかを把握しておきましょう。
モデルでテストすべきは以下の点です。
- メッセージを保存できる場合
- メッセージと画像があれば保存できる
- メッセージがあれば保存できる
- 画像があれば保存できる
- メッセージを保存できない場合
- メッセージも画像も無いと保存できない
- group_idが無いと保存できない
- user_idが無いと保存できない
ここでわかるのが「メッセージが保存できる場合とできない場合」の2つで分けることができます。
このように特定の条件で分ける場合は可読性を高めるために、
「context」を使った方がいいです。
contextは以下のように使います。
require 'rails_helper' RSpec.describe Message, type: :model do describe '#create' do context 'can save' do # この中にメッセージを保存できる場合のテストを記述 end context 'can not save' do # この中にメッセージを保存できない場合のテストを記述 end end end
メッセージの保存ができる場合とできない場合を場合分けできたので、
中のテスト記述をそれぞれ考えていきます。
テストの記述方法は、下の例をみて参考にしてください。
describe "hogehoge" do it "1 + 1は2になること" do expect(1 + 1).to eq 2 end end
まず、describeというキーワードでテストをグループ化します。
(これはもう書いてありますね)
続いて、テスト1つ(example)として評価される
it do ~ endのブロックの中に、expect(X).to eq Yという形式の式を書いていきます。
これが、実際にテストが成功するかどうかチェックされる式(エクスペクテーション)になります。
eqの部分はマッチャと言って、テストが成功する条件を示します。例えばeqは「等しければ」という意味になります。つまり上の文であれば、「1+1が2と等しければ成功」ということになります。
他にもinclude(含んでいれば)、valid(バリデーションされれば)など複数のマッチャが存在します。
[ メッセージを保存できる場合]
先程の項目を上からやっつけていきたいと思います!
まず1個目、
①「メッセージと画像があれば保存ができる」
このように保存ができるという条件をクリアする場合のテストを使いたい時は、
be_validマッチャを利用します。
使ってみます。
it 'is valid with content and image' do expect(build(:message)).to be_valid end
it と do の間にテストの説明文を書きます。
(ちなみに「内容とイメージがあれば有効」的なことを書いてます。)
2行目にあるbuild(:message)は実は他のファイルにmessageとして作成したインスタンスの情報をあらかじめ設定していれることで省略して使えるようにしてます。
(前回の記事で少し触れてるので戻ってみてください)
factoriesというディレクトリを作って、
そのなかで作成したいインスタンス名からとってmessages.rbというファイルを作成し
設定したいインスタンスの情報を記述しています。
これで既にファクトリーの定義が完了しているため、build(:message)でMessageモデルのインスタンスを生成することができます。
これで1つ目のテストコードが完成です。
it 'is valid with content and image' do
expect(build(:message)).to be_valid
end
|
②メッセージがあれば保存できる。
これも保存できちゃうかを調べるので be_validを使う。
it 'is valid with content' do expect(build(:message, image: nil)).to be_valid end
buildで設定したインスタンスを持ってきながら、
image: nilで情報の上書きもできます。
これで画像はないけどメッセージはあるという情報をテストできます。
③画像があれば保存できる。
これも②と同じですね、上書きの部分でcontent: nilにすればオッケー
it 'is valid with image' do expect(build(:message, content: nil)).to be_valid end
[保存ができない場合]
次に保存ができない場合について調べてみます。
今度は逆に保存ができない状態になってるか?について調べたいですね。
そういうときはvalid?メソッドを使います。
①画像もメッセージもないと保存できない
it 'is invalid without content and image' do message = build(:message, content: nil, image: nil) message.valid? end
最初にcontentもimageもないインスタンスを生成します。
そして2行目で保存ができないようになっているか確認します。
it 'is invalid without content and image' do message = build(:message, content: nil, image: nil) message.valid? expect(message.errors[:content]).to include('を入力してください') end
valid?メソッドを利用したインスタンスに対して、errorsメソッドを使用することによって、バリデーションにより保存ができない状態である場合なぜできないのかを確認することができます。
contentもimageもnilの今回の場合、'を入力してください'というエラーメッセージが含まれることが分かっているため、includeマッチャを用いて以下のようにテストを記述することができます。
これで「メッセージも画像もない場合は保存できずに'入力してください'というエラーがでるかどうかをテストする」ことができます。
②group_idが無いと保存できない
③user_idが無いと保存できない
これも①とほとんど一緒です。
上書きしたいところをnilにして、入力してくださいというエラー文がでるかを確認してください。
it 'is invalid without group_id' do message = build(:message, group_id: nil) message.valid? expect(message.errors[:group]).to include('を入力してください') end it 'is invaid without user_id' do message = build(:message, user_id: nil) message.valid? expect(message.errors[:user]).to include('を入力してください') end
うん、ほぼ一緒ですね笑
テストしてみる
$ bundle exec rspec spec/models/message_spec.rb
ターミナルでこのコマンドを打つと、テストを実行できます!
これでエラーが起きなければ完成です!
ターミナルはこんな感じでテストできました!