【RSpec】メソッドスタブに関するメモ

RSpecでメソッドをスタブする方法を整理してみます（expect等でテストするところはまでは言及していません）。
 # 色々調べながら試しながら整理してみましたが、イマイチ自信無い感じなのでツッコミ歓迎です..:bow: 

# サンプルクラス

```ruby:aki.rb
class Aki
  def self.test_aki
    "test_aki"
  end

  def test
    "instance test"
  end
end
```

# 方向性

スタブには `allow` メソッドを使うことになりますが、大きな分類として、以下があるように思いました。

- Doubleオブジェクトにメソッドを追加し、そのメソッドをスタブする
    - `double` メソッド
    - `class_double` メソッド
- 実際のリソースをスタブする
    - オブジェクトのメソッドをスタブする
    - クラスのメソッドをスタブする

## Doubleオブジェクトにメソッドを追加し、そのメソッドをスタブする

### `double` メソッド（空のモックオブジェクトにメソッドをスタブする）

単純な `double` メソッドを使う場合です。これはDoubleオブジェクトを作成します。Doubleお弁当は空のモックオブジェクトで、任意のメソッド名のスタブを作れます。
こちらは以下の通り空のモックオブジェクトになるので、5行目の `allow` で偽のメソッドをスタブします。
ちなみに出来上がったオブジェクトは実際の `Aki` クラスに紐付いているわけでは無く、 `double` メソッドの引数は任意の文字列で問題ありません。
また、スタブするメソッドのメソッド名等にも制限はありません。

```ruby:rspec
  it "double" do
    aki_double = double("aki")
    pp aki_double # => #<Double "aki">
    pp aki_double.class # => <Double "aki">
    allow(aki_double).to receive(:test_aki).and_return("stub test")
    allow(aki_double).to receive(:test_aki_hoge).and_return("stub test") # 実際には実装されていないメソッド名もスタブ化可能
    pp aki_double.test_aki # => "stub test"
  end
```

#### 参考リンク
Test Doubles - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/basics/test-doubles


## `class_double` メソッド

`class_double` を用いる場合のサンプルは以下の通りです。
基本的には上記 `double` と似たようなイメージですが、 `class_double` を用いると、実際のクラスの情報と紐付いて実際のクラスに実装されていないメソッドのスタブ化はできないようになります。
ただし、以下の場合は、その実行環境全体でそのクラスに対してスタブが効いたわけではなく、あくまで `class_double` の返り値で `allow` したオブジェクトのみスタブされている（下から3行目の通り、普通にクラスメソッドを呼んだ場合は、実際のメソッドが実行されている）。

```ruby
  it "class_double" do
    pp Aki # Aki
    aki_double = class_double("Aki")

    pp aki_double.is_a?(Object) # => true
    pp aki_double.is_a?(Class) # => false

    pp Aki # Aki
    pp Aki.test_aki # => test_aki
    pp aki_double # => #<ClassDouble(Aki) (anonymous)>
    # pp aki_double.test_aki # まだメソッドをスタブ化していないため、これはエラーになる => ：<ClassDouble(Aki) (anonymous)> received unexpected message :test_aki with (no args)

    # スタブ
    allow(aki_double).to receive(:test_aki).and_return("stub test")
    # allow(aki_double).to receive(:test_aki_hoge).and_return("stub test") # 実際には存在しないメソッド名のためエラーとなる => the Aki class does not implement the class method: test_aki_hoge

    # クラス
    pp Aki.test_aki # => test_aki
    pp aki_double.test_aki # => "stub test"
  end
```

### 参考

Using a class double - Verifying doubles - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles/using-a-class-double


### `as_stubbed_const` オプション
上記の場合は、その実行内のすべてのクラスに対してスタブされたわけでは無かったのだが、 `class_double` する際に `as_stubbed_const` オプションを付与するとクラス（の定数というのだろうか？）自体をスタブできる。

```ruby
  it "class_double as_stubbed_const" do
    pp Aki # Aki
    Aki.test_aki # test_aki
    aki_double = class_double("Aki").as_stubbed_const
    allow(aki_double).to receive(:test_aki).and_return("stub test") # allowしないと `test_aki` メソッドは定義されていないものとしてエラーとなる

    pp aki_double # #<ClassDouble(Aki) (anonymous)>
    pp Aki # #<ClassDouble(Aki) (anonymous)> ★as_stubbed_constをすると定数としてのクラスを置き換える
    pp Aki.test_aki # stub test
  end
```

## 実際のリソースをスタブする
### 実際のオブジェクトのメソッドをスタブする

実際のクラスのオブジェクトに対しても `allow` を実施し、メソッドをスタブ出来ます。この場合は、Doubleの時と違い、実際に無いメソッド名のメソッドはスタブ化できません。

```ruby
  it "Allow(Object)" do
    aki = Aki.new
    pp aki # #<Aki:0x0000563629e5f848>
    pp aki.test # => "instance test

    # これだけでは影響なし
    allow(aki)
    pp aki # => #<Aki:0x0000563629e5f848>
    pp aki.test # => "instance test

    # スタブ（返り値なし）
    allow(aki).to receive(:test)
    pp aki.test # => nil

    # スタブ（返り値あり）
    allow(aki).to receive(:test).and_return("stub test") # 実際に存在するメソッドだから大丈夫
    #  allow(aki).to receive(:test2).and_return("testtest") # 存在しないメソッドだからダメ…　#<Aki:0x000055a896306c88> does not implement: test2
    pp aki # #<Aki:0x0000563629e5f848> オブジェクト自体は変わっていない
    pp aki.test # => stub test"
  end
```

ちなみにこの方法は `Partial test doubles` というらしいです（日本語だと何と呼ぶのがいいのだろう）。

> A partial test double is an extension of a real object in a system that is instrumented with
test-double like behaviour in the context of a test

> Partial test doubles - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/docs/basics/partial-test-doubles

この方法はSpyという方法で、メソッドが呼ばれたかを検証するのによく使われると思います。

Spies - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/v/3-8/docs/basics/spies#spy-on-a-method-on-a-partial-double

rspec3でメソッドが呼び出されたかテストする - Qiita https://qiita.com/_am_/items/398b0782fa3754ff3878

### 実際のクラスメソッドをスタブする

実際に存在するクラスに対しても実施可能です。こちらも実際に無いメソッドのスタブは出来ません。

```ruby
  it "Allow(Class)" do
    pp Aki # => Aki
    pp Aki.test_aki # => "test_aki"
    allow(Aki).to receive(:test_aki).and_return("stub test") # 実際に存在するメソッドだから大丈夫
    #  allow(Aki).to receive(:test2).and_return("testtest") # 存在しないメソッドだからこの時点で怒られる Aki does not implement: test2
    pp Aki # => Aki
    pp Aki.test_aki # => "stub test"
  end
```

Allowing messages - Basics - RSpec Mocks - RSpec - Relish https://relishapp.com/rspec/rspec-mocks/v/3-8/docs/basics/allowing-messages


# おわり
ほしい：
Qiita > 要望 > コード左側に行番号を表示 - Qiita https://qiita.com/7of9/items/e65f2f36c817ab965284

# その他参考


使えるRSpec入門・その3「ゼロからわかるモック（mock）を使ったテストの書き方」 - Qiita https://qiita.com/jnchito/items/640f17e124ab263a54dd