RSpec

【RSpec】マッチャ合成式を書く場合はDefine negated matcherが便利

RSpec

 

今回はRSpecでマッチャ合成式を書く際に便利だったDefine negated matcherについて紹介したいと思います。マッチャ合成式で否定系(not_to)をテストしたい場合に重宝しそうです。

マッチャ合成式とは

マッチャ合成式(Compound Matcher Expressions)とは、RSpec3から導入された機能で、1つのitに複数の期待するテストを書くことができる手法です。複数の期待するテストはandやorを繋げて記述することができます。

例えば、アルファベットがaから始まりzで終わるテストを書くとします。マッチャ合成式を使用しない場合は以下のように2つのexpectを記載しなければなりません。

# マッチャ合成式を使用しない場合

it 'アルファベットがaから始まりzで終わること' do
  expect(alphabet).to start_with("a")
  expect(alphabet).to end_with("z")
end

 

しかしマッチャ合成式を使用することで以下のように記述することができます。

# マッチャ合成式を使用した場合

it 'アルファベットがaから始まりzで終わること' do
  expect(alphabet).to start_with("a")
    .and end_with("z")
end
andは連結したテストの両方を満たす場合にパスします。

 

どれかのテストがパスすることを期待するorもあります。

以下は信号機の色が赤/緑/黄のどれかであることを期待するテストになります。

it '信号機の色が赤/緑/黄のどれかであること' do
  expect(stoplight.color).to eq("red")
     .or eq("green")
     .or eq("yellow")
end

 

このようにマッチャ合成式(Compound Matcher Expressions)を使用することで連結してテストを書くことができ、冗長なコードを減らすことが可能になります。

Define negated matcherとは

先ほど紹介したマッチャ合成式(Compound Matcher Expressions)は便利ですが、その一方で否定系のテスト(not_to)を書くことができません。

it 'アルファベットがaから始まりzで終わらないこと' do
  expect(alphabet).not_to start_with("a")
    .and end_with("z")
end

=> NotImplementedError:
   `expect(...).not_to matcher.and matcher` is not supported, since it creates a bit of an ambiguity. Instead, define negated versions of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and use `expect(...).to matcher.and matcher`.

 

ここで使えるのがDefine negated matcherになります。Define negated matcherを使用することで、否定系のマッチャ合成式を書くことができるようになります。

実装手順

今回は「アルファベットがaから始まりzで終わらない」ことをDefine negated matcherを使用してテストしてみます。

it 'アルファベットがaから始まりzで終わらないこと' do
  expect(alphabet).not_to start_with("a")
    .and end_with("z")
end

=> NotImplementedError:
   `expect(...).not_to matcher.and matcher` is not supported, since it creates a bit of an ambiguity. Instead, define negated versions of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and use `expect(...).to matcher.and matcher`.

matchers.rbの作成

まずは、Define negated matcherを定義するためのファイルであるmatchers.rbをspec/support配下に作成しましょう。

% mkdir spec/support
% touch spec/support/matchers.rb

Define negated matcherの定義

作成したmatcher.rbに否定系にしたいマッチャを記載します。

第一引数に定義したいマッチャを、第二引数には既存のマッチャを指定します。今回はstart_withマッチャとend_withマッチャをnot_start_withとnot_end_withというマッチャ名に変更します。

# 第一引数に定義したいマッチャを、第二引数には既存のマッチャを指定する
RSpec::Matchers.define_negated_matcher :not_start_with, :start_with
RSpec::Matchers.define_negated_matcher :not_end_with, :end_with

matchers.rbの読み込み

続いて、matchers.rbを読み込む記述をrails_helper.rbに追記します。

# rails_helper.rb

# 以下の1行を追記
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

テスト内容の修正

最後にテストを以下のように変更します。matchers.rbを読み込むため、冒頭にrequire 'rails_helper'の記載を忘れないよう注意しましょう。

require 'rails_helper'

it 'アルファベットがaから始まりzで終わらないこと' do
  expect(alphabet).to not_start_with("a")
    .and not_end_with("z")
end

 

これで「アルファベットがaから始まりzで終わらないこと」のテストがパスすることが確認できます。

まとめ

  • マッチャ合成式とは、1つのitに複数の期待するテストを書くことができる手法
  • andは連結したテストの両方を満たす場合にパスする
  • orは連結したいずれかのテストを満たす場合にパスする
  • Define negated matcherは、マッチャの条件を反転させたものを新たに定義できる機能
  • 第一引数に定義したいマッチャを、第二引数には既存のマッチャを指定する

参考

Compound Matcher Expressions

 

 

今回はRSpecでマッチャ合成式を書く際に便利だったDefine negated matcherについて紹介しました。マッチャ合成式で否定系(not_to)をテストする場合だけでなく、一連の流れをテストしたい場合にも使えそうですね。