Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

RSpecのDefine negated matcherが地味に便利

RSpecのDefine negated matcherが地味に便利でした。
特にマッチャ合成式を書くときに便利です。

マッチャ合成式(Compound Matcher Expressions)とは

andやorを使って、マッチャをつなげて書くことができます。
https://rspec.info/blog/2014/01/new-in-rspec-3-composable-matchers/

適当ですが、Shopというモデルのインスタンスメソッド receive_order を実行した結果、CustomerモデルとOrderモデルのレコードが作成されることを確かめるテストがあるとします。

it 'customerとorderを作る' do
  expect { shop.receive_order }.to change(Customer, :count).by(1)
  expect { shop.receive_order }.to change(Order, :count).by(1)
end

 
上記は、マッチャ合成式を使うと以下のように書くことができます。
無駄な繰り返しが無くなって、コード量が減ります。 👏

it 'customerとorderを作る' do
  expect { shop.receive_order }.to change(Customer, :count).by(1)
    .and change(Order, :count).by(1)
end

Define negated matcherとは

マッチャの否定形を作ることができます。

https://relishapp.com/rspec/rspec-expectations/docs/define-negated-matcher

Define negated matcherの何が便利か

使用例

先ほどのreceive_orderのテストで、Productモデルのレコードは作られないこともテストしたいとします。
このように書くと、テストは失敗します。

it 'customerとorderを作るがproductは作らない' do
  expect { shop.receive_order }.not_to change(Product, :count)
    .and change(Customer, :count).by(1)
    .and change(Order, :count).by(1)
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`.

マッチャ合成式を使っている時にnotは使えないみたいです…
テストを分割すればエラーになりませんが、できれば1つにまとめて、すっきりとさせたい気持ちがあります。

it 'customerとorderを作る' do
  expect { shop.receive_order }.to change(Customer, :count).by(1)
    .and change(Order, :count).by(1)
end

it 'productは作らない' do
  expect { shop.receive_order }.not_to change(Product, :count)
end

ここでDefine negated matcherを使います。
下記のようにchangeの否定系のマッチャを定義します。

# 第一引数に定義する否定系のマッチャ
# 第二引数に否定系にしたい既存のマッチャ
RSpec::Matchers.define_negated_matcher :not_change, :change

定義する場所は、マッチャを使いたいテストがあるspecファイルの先頭やspec/supportmatchers.rbなど好きなファイル名で作成すると良いと思います。

上で定義したchangeの否定系のマッチャを使えば、先ほどのテストは以下のように書くことができます。

it 'customerとorderを作るがproductは作らない' do
  expect { shop.receive_order }.to not_change(Product, :count)
    .and change(Customer, :count).by(1)
    .and change(Order, :count).by(1)
end

# もちろん and の後にも書ける

it 'customerとorderを作るがproductは作らない' do
  expect { shop.receive_order }.to change(Customer, :count).by(1)
    .and change(Order, :count).by(1)
    .and not_change(Product, :count)
end

以上、Define negated matcherが便利だと思った話でした。🍵

harashoo
焼肉と寿司
pepabo
「いるだけで成長できる環境」を標榜し、エンジニアが楽しく開発できるWebサービス企業を目指しています。
https://pepabo.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away