では、borrowing_spec.rbに「サンプル(example)」を追加してみましょう。以下の例では「「Borrowing」は「user_id」なしでバリデーションに失敗する」という振る舞いを定義します。
require 'rails_helper' RSpec.describe Borrowing, :type => :model do it "isn't valid without user_id" do borrowing = Borrowing.new borrowing.user_id = nil expect(borrowing).not_to be_valid end end
まず3行目のRSpec.describeでは振る舞いを記述する対象を明らかにしています。4〜8行目の「it」メソッドが「サンプル(example)」であり、ブロックに期待する振る舞いを定義します。
ここでは変数「borrowing」に「Borrowing」モデルのインスタンスが生成・代入されており、そのuser_id属性が空であると「borrowing」がバリデーションをパスしないことを定義しています。
7行目の式は振る舞いが定義通りかをチェックする「エクスペクテーション」と呼ばれるRSpecの機能です。これについては後ほど詳しく紹介します。
6行目でわざわざ「nil」を代入していますが、例えば「Borrowing」モデルが生成時に「user_id」を特定の値で初期化するコールバックを実装された場合でも、定義する振る舞いを保証するためです。
このように、振る舞いの定義には見落としがちなポイントがあるため「前提となる状況・状態(Given)」「発生するイベント(When)」「その結果どうなるか(Then)」を意識するといいでしょう。
それでは、この「サンプル(example)」を実行してみましょう。以下のような出力が得られるはずです。
F Failures: 1) Borrowing isn't valid without user_id Failure/Error: expect(borrowing).not_to be_valid expected #<Borrowing id: nil, user_id: nil, book_id: nil, created_at: nil, updated_at: nil> not to be valid # ./spec/models/borrowing_spec.rb:7:in `block (2 levels) in <top (required)>' Finished in 0.00586 seconds (files took 1.7 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/models/borrowing_spec.rb:4 # Borrowing isn't valid without user_id
この出力によると「Borrowing isn't valid without user_id」という先ほど記述したサンプルで失敗しています。失敗した位置も示されており、ここでは「borrowing_spec.rb」の7行目で発生しています。
なぜ、この「サンプル(example)」が失敗したのかというと「Borrowing」モデル自体にはまだバリデーションの実装をしていないからです。そこで「app/models/borrowing.rb」を次のように実装します。
class Borrowing < ActiveRecord::Base belongs_to :user belongs_to :book validates :user_id, presence: true end
これにより「Borrowing」モデルは「user_id」が空のときバリデーションに失敗するようになりました。それでは再度「rspec」コマンドでスペックを実行してください。失敗しなくなっているはずです。
ここまでがRSpecで振る舞いを定義してチェックするというRailsのテストの基本的な流れになります。
ここからはRSpecの文法について解説します。文法といってもRSpecはRubyベースの「ドメイン特化言語(DSL)」なのでRubyの文法に従います。ドメイン特化言語とは特定の領域で使われる言語のことであり、RSpecは振る舞いを記述する領域で使われる言語です。
そのため、RSpecの文法は「振る舞いを記述するための文法」ということになります。
複数の「サンプル(example)」が関係し合っている場合、その関係を説明して「グループ化」しておくと見通しがよくなります。言い換えれば、あるオブジェクトのサンプルをそのオブジェクトのサンプルグループとしてまとめておくことが「describe」メソッドでできます。
例えば、次のように「Borrowing」モデルについて「RSpec.describe」メソッドのブロックで複数のサンプルを定義できます。
RSpec.describe Borrowing, :type => :model do it "isn't valid without user_id" do borrowing = Borrowing.new borrowing.user_id = nil expect(borrowing).not_to be_valid end it "isn't valid without book_id" do borrowing = Borrowing.new borrowing.book_id = nil expect(borrowing).not_to be_valid end end
さらに「describe」メソッドは入れ子にしたり、複数含めたりすることもできます。
RSpec.describe Borrowing, :type => :model do describe 'without user' do it 'is not valid' do borrowing = Borrowing.new borrowing.user = nil expect(borrowing).not_to be_valid end end describe '#overdue?' do it "returns false when due_back is tomorrow and attribute is default(today)." do borrowing = Borrowing.new(due_back: 1.days.since) expect(borrowing.overdue?).to be(false) end end end
また、「describe」のエイリアスに「context」があります。上のコードの2行目のようにコンテキストを説明したい場合は「describe」より「context」の方がいいでしょう。
余談ですが、ここではトップレベルでの「describe」メソッドはRSpecモジュールから呼び出しています。これができるようになったのはRSpec 3からです。
RSpec 3より前のバージョンでは拡張された「main」オブジェクトの「describe」メソッドをレシーバなしで呼び出していました。
RSpec 3からは「main」オブジェクトなどへの拡張を設定でオフにでき、その際エラーにならないようにするためにモジュールから「describe」メソッドが呼ばれています。
RSpec 3でもトップレベルの「main」オブジェクトから「describe」メソッドを呼び出せますが、ブロックの中からはRSpecモジュールの「describe」を呼び出せないので注意してください。
サンプルを作るには「it」メソッドを使います。これは振る舞いを説明する文字列を引数にとり、振る舞いの定義をオプションのブロックで行います。
ブロックなしで呼び出すと、「保留のサンプル」としてログに出力されます。
it "will be deleted when user deleted" #=> Pending: Borrowing will be deleted when user deleted # Not yet implemented # ./spec/models/borrowing_spec.rb:18
また、「it」にもエイリアスの「specify」があります。「it」を主語として引数の文字列に述語を書くことで何のサンプルであるかが分かりやすくなりますが、「it」では説明しにくいような場合では「specify」を使うといいでしょう。
Copyright © ITmedia, Inc. All Rights Reserved.