RSpec の satisfy マッチャを便利に使う
Model のテストなどを書く時に次のように任意のリレーションに対して意図するクエリが追加されているかどうかをテストすることがあると思います。
class User < ActiveRecord::Base # rate 基準で上位10人を絞り込む scope :top10, -> { where(active: true).order(:rate).limit(10) } end RSpec.describe User do describe ".top10" do # scope top10 に対して意図するクエリが追加されていることをテストする subject { User.top10.to_sql } it { expect(subject.scan('WHERE "users"."active" = TRUE').one?).to be_truthy } it { expect(subject.scan('ORDER BY "users"."rate" ASC').one?).to be_truthy } it { expect(subject.scan('ASC LIMIT 10').one?).to be_truthy } end end
テストとしては別に問題ないと思うんですが、 expect
に負担がかかり過ぎてますね。
これを expect
に負担をかけるのではなくてマッチャでがんばって書きたいと思います。
satisfy
マッチャを使う
RSpec には satisfy
マッチャがあります。
これは expect
に渡した値を引数としたブロックを渡し、テストが成功しているか失敗しているかを判定することができるようになります。
例えば、次のように利用することができます。
describe "test" do # satisfy に渡したブロックが真を返せばテストがパスする # x = 1 it { expect(1).to satisfy { |x| x.odd? } } # x = 2 it { expect(2).to satisfy { |x| x.even? } } end
先程の Model のテストは satisfy
を利用すると次のように書くことができます。
RSpec.describe User do describe ".top10" do subject { User.top10.to_sql } # is_expected で書くことができる! it { is_expected.to satisfy { |sql| sql.scan('WHERE "users"."active" = TRUE').one? } } it { is_expected.to satisfy { |sql| sql.scan('ORDER BY "users"."rate" ASC').one? } } it { is_expected.to satisfy { |sql| sql.scan('ASC LIMIT 10').one? } } end end
更にヘルパメソッドを定義することでオレオレマッチャみたいなのをさくっと定義することもできます。
RSpec.describe User do describe ".top10" do # let っぽく記述 define_method(:scan_once) { |query| satisfy { |sql| sql.scan(query).one? } } subject { User.top10.to_sql } # 簡略化してかける it { is_expected.to scan_once 'WHERE "users"."active" = TRUE' } it { is_expected.to scan_once 'ORDER BY "users"."rate" ASC' } it { is_expected.to scan_once 'ASC LIMIT 10' } end end
これは便利。