kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

mockとstubが難しい件 RSpec 3.6

かずまです。
いろいろ考えることや、やれることも増えてきて楽しい半面悩ましいです。

Rubyをテストする際にmini testやRspecを使って検証することが多いと思います。
その中でもメソッドをモックする、スタブにするということを覚えました。

追記

PM兼PM on Twitter: "@kazumalab つ https://t.co/Wfvu46XrAa
これ聴くと、もうちょっと視野が広がりそうな一方、混乱するかも笑"

https://ajito.fm/6/

Mockの知識が広がるのを教えて頂きました。

ここのT-wadaさんとの対談を聞いてmockの使い方の歴史を知り感動しました。

たしかにMockはクラスの設計時に使うというのは興味深いし、使えそうです。

僕らが使ってたのはtest doubleのサブクラスのmockだったんですね。勉強になりました。

モックとスタブの概念

結構難しいので説明はできませんのでこれの回答っぽいリンクを貼っておきます。

TDD > モック / スタブ - Qiita

どちらとも共通しているのが、「あるオブジェクトのメソッドが呼ばれたときに
実際にそのメソッドを実行せずに自分で返ってきてほしいものを指定する。」というところだと思っています。

今の考えはこれですね。

ただし、モック、スタブにする部分はそのオブジェクトの振る舞いがすでにテストされていて暗黙的に理想とするデータを返す状態になっていることを前提としていると思っています。つまり、テストをしているメソッドのテストが完璧だったとしてもモックしたメソッドが不十分な振る舞いであれば足を引っ張るのはそのメソッドになるということです。

なので、それぞれのメソッドに対してユニットテストは書くべきだと思っています。

どうやって使うのか?

すでに使っているのであればこれ以降読む必要は無いと思います。
わかりやすいレシピ集を載せておきます。(3.3ですが。)

dev.classmethod.jp


僕が使う例があっているかどうかという部分は確かではありませんが、
次の例を出してサンプルとします。

ex. Railsだとcurrent_userにログインしているユーザー情報が入る場合のお話

ApplicationController.current_userで返ってくるものがnilのときはログインしていない状態、
userが返って来ればログインしている状態です。

class ApplicationController
  def current_user
    # return login user
  end
end


次のようなテストを書いたとします。
この場合、userの詳細ページにアクセスできればテストはsuccessです。

しかしUserControllerのbefore_actionにはログインしているかチェックするメソッドをセットしているので、
このテストは失敗します。

そこでテストでもログインさせる処理が必要です。
もちろん、sign_in_pathにユーザーのログイン情報をpostしてログインするのもありだと思います。
ただ、ログインするテストをしていれば、データをわざわざpostしなくてもいいわけです。(?)

今回はApplicationControllerのインスタンスメソッドをモック、もしくはスタブ化するので、
次のように書いていきます。

relishapp.com

Mock化はexpect

Stub化はallow

2つの違いは難しいのですが、

jangajan.com

ココらへんいいかと思います。
メソッドが呼ばれることを検証したい場合はexpect、
しない場合はallowでいいようです。

もちろんexpectを使うとcurrent_userが呼ばれない場合は
このテストは失敗します。
ちなみに、expectを使うと何度呼ばれたか?というのも検証できるので便利です。

relishapp.com

onceとかtwiceを使うのですね。

他にも

  • test_double
  • double
  • spy

などといった、インスタンス自身をモック、スタブ化するものもあるようです。
relishapp.com

テスト時にインスタンスを生成し、
引数にオブジェクトを渡して、そのメソッド内で渡したインスタンスのメソッドを呼ぶ場合などに
test_doubleを使って、そのメソッドをモックすることができます。

ここをみるとdoubleはtesto_doubleをincludeしてます...。
何をやっているかは深く読んでないのでここでは何も言えません。
ちょっとあとで読んでみます。

https://github.com/rspec/rspec-mocks/blob/master/lib/rspec/mocks/test_double.rb


さて、実際にスタブ化してみます。
今回はメソッドが呼ばれたことを検証しないのでallow_any_instance_ofを使います。

これでテストが成功しました。

最後に

今回は例としてログインの部分をスタブ化して、擬似的にログインしてる形を作りました。
いい例だとは思えませんが、こんな感じでできる!と言うのはつかめると思います。

Gemを作る場合やAPIを使う場合に外部との通信が入ってしまったりする部分にmockを使ったりします。
APIなどはとくにAPIサーバー側でアクセス回数制限を設けている場合がほとんどなのでテストで消費するのはもったいないですよね。

OauthなどはMockするためのものが用意されているのでそれを使うといいですね。
他にも

github.com

VCRという一度だけ外部と通信を行って、それをymlにダンプしてそれ以降はそれを使って
テストを実行していくという便利なgemもあります。
まだ使ったことはありませんが、mockデータを作るのは面倒なので、一度使ってみたいものです。

github.com
webmockというそもそもhttp accessをもモックしてしまうものもあるので
世の中便利ですね。

間違っている部分や疑問に思っていることがありましたら、
かずま (@kazumalab) | Twitter
か、こちらのコメントまでお寄せください。