学習のアウトプットとして投稿しています。
何かご指摘がございましたらお願いいたします。
モックとスタブとは
■ モック(mock)
モックは本物のオブジェクトのふりをするオブジェクトのこと。テストで用いられる。
これまで、FactoryBotや素のRubyを使って作成していたテストデータの代わりになるもの。
両者の違いはDBにアクセスするかしないか。モックはDBにアクセスしない。
そのため、テスト時間を短縮できる。
テスト対象でないオブジェクトなどは、モックで管理した方がよい場合がある。
この場合、テスト対象が関連オブジェクトの実装に影響されにくくなり、管理しやすいテストコードとなる。
■ スタブ(stub)
スタブはオブジェクトのメソッドを上書きして、事前に返して欲しい値を設定することができる。
なので、スタブはテストでダミーメソッドとして使うことができる。
使い方
class User < ApplicationRecord
has_one :profile
delegate :name, to: :profile
end
class Profile < ApplicationRecord
has_one :user
def name
[first_name, last_name].join(" ")
end
end
ここでUserモデルのnameメソッドをモックとスタブを使ってテストした場合
RSpec.describe User, type: :model do
# モックを使う
let(:profile) { double("profile", name: "Test User") }
it "ユーザーのフルネームを返す" do
user = User.new
# スタブを使う
allow(user).to receive(:profile).and_return(profile)
expect(user.name).to eq "Test User"
end
end
profileにモックを使い、引数にメソッドが呼ばれた時の返り値も設定しておく。
userにはprofileを受け取ったら、モックのprofileを返すようスタブを使い設定する。
こうすることで、本来userオブジェクトはprofileを受け取ったらDBに問い合わせてprofile_idからprofileをとってくるところを、スタブを使いモックのprofileを返すと設定したのでDBに問い合わせることなく、実行スピードが早くなる。
さらに、このUserモデルのテストは、モックを使ったことでprofileのnameメソッドの実装に依存しなくなるため、profileモデルの実装から影響を受けにくくなり管理しやすいテストになった。
注意点
このテストは、モックのprofileしか見ていないため、例えばprofileモデルのnameメソッドの名前が変わったり、削除されたりしてもこのテストは通ってしまう。
コードの変更によるバグなどはテストでキャッチするべきなので、このままではよくない。
この問題に対処するため、検証機能付きのモックを使う。検証機能付きのモックは引数で与えられたモデルのインスタンスと同じように振る舞うので、nameメソッドがなかった場合はエラーを出してくれる。
先ほどのテストに検証機能付きのモック(instance_double)を使った場合
※引数のprofileは大文字で記載する
RSpec.describe User, type: :model do
# 検証機能付きのモックを使う(引数のProfileは大文字で記載する)
let(:profile) { instance_double("Profile", name: "Test User") }
it "ユーザーのフルネームを返す" do
user = User.new
# スタブを使う
allow(user).to receive(:profile).and_return(profile)
expect(user.name).to eq "Test User"
end
end
まとめ
テストデータの作成はFactoryBotやRubyでしか作成したことがなかったので、テスト時間が早くなるということでオリジナルアプリのテスト作成にモック導入を検討してみようかなと思いました。ただテスト初心者なので使いやすそうなinstance_doubleからやっていきたいですね。。。
参考
Everyday Rails - RSpecによるRailsテスト入門
使えるRSpec入門・その3「ゼロからわかるモック(mock)を使ったテストの書き方」
Railsガイド 3.4 メソッドの委譲