パーフェクト Ruby on Rails 第7章のRailsアプリケーションのテストで
何箇所かハマったところがあったので、対処方法まとめときます。
ただし、その場凌ぎの対処ばかりで根本的な対処方法ではないと思いますのでご注意ください。
また、環境は必ずしも書籍通りになっていません。
(なるべく最新のを入れました)
- Rails 4.2.4
- RSpec 3.4.0
(0) 前書き
書籍では、RSpec 3.0.0.beta2 を使用しているようです。
ハマったわけではないですが、% ./bin/rails g rspec:model event
を行った時に生成される雛形がそもそも違いました。
# 書籍だと...
require 'spec_helper'
describe Event do
pending "add some examples to (or delete) #{__FILE__}"
end
# 私の環境(RSpec 3.4.0)では...
require 'rails_helper'
RSpec.describe Event, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end
よって、自分の判断で適宜置き換えています。
(1) shoulda-matchersでNoMethodErrorの対処
とりあえず、何も考えずに最新のshoulda-matchersを入れる
gem 'shoulda-matchers' # 3.0.1 が入った
require 'rails_helper'
RSpec.describe Event do
describe '#name' do
# shoulda-matchers 3.0.1だとここでエラーになる
it { should validate_presence_of(:name) }
it { should ensure_length_of(:name).is_at_most(50) }
end
# 中略
end
Failures:
1) Event#name
Failure/Error: it { should validate_presence_of(:name) }
NoMethodError:
undefined method `validate_presence_of' for #<RSpec::ExampleGroups::Event::Name:0x007fe22ee02cd0>
# ./spec/models/event_spec.rb:5:in `block (3 levels) in <top (required)>'
バージョンを書籍通りにしたら通った。
gem 'shoulda-matchers', '~> 2.6.0'
参考サイト
(Qiita) shoulda-matchers 2.8.0で undefined method `validate_presence_of'などと出たので。
(2) rails c
でFactoryGirl.create(:event)
を実行すると失敗する
./bin/rails c
を実行し、FactoryGirl.create(:event)
を実行してみますと書いてあったので実行した。
最初は問題なく成功。
ただ何度かやってるとエラーが発生するようになった。
[5] pry(main)> FactoryGirl.create(:event)
(0.1ms) begin transaction
SQL (1.6ms) INSERT INTO "users" ("provider", "uid", "nickname", "image_url", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["provider", "twitter"], ["uid", "uid5"], ["nickname", "nickname5"], ["image_url", "http://example.com/image5.jpg"], ["created_at", "2016-01-02 03:21:35.543438"], ["updated_at", "2016-01-02 03:21:35.543438"]]
SQLite3::ConstraintException: UNIQUE constraint failed: users.provider, users.uid: INSERT INTO "users" ("provider", "uid", "nickname", "image_url", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)
(0.1ms) rollback transaction
ActiveRecord::RecordNotUnique: SQLite3::ConstraintException: UNIQUE constraint failed: users.provider, users.uid: INSERT INTO "users" ("provider", "uid", "nickname", "image_url", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)
from /Users/yuta/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/sqlite3-1.3.11/lib/sqlite3/statement.rb:108:in `step'
エラーメッセージを見る限りUserモデルの登録しようとしているレコードがユニークじゃないよといった感じか。
マイグレーション用ファイルでは、
# db/migrate/XXXXXXXXXXXXXXX_create_users.rb
add_index :users, [:provider, :uid], unique: true
と指定してて、factory_girlのUserモデル用の定義ファイルでは、
# spec/factories/users.rb
FactoryGirl.define do
factory :user, aliases: [:owner] do
provider 'twitter'
sequence(:uid) { |i| "uid#{i}" }
sequence(:nickname) { |i| "nickname#{i}" }
sequence(:image_url) { |i| "http://example.com/image#{i}.jpg" }
end
end
provider
は固定なので、uid
が被っちゃってると。
そうすると、#sequence
が自分の想像と動きが違ってそうな気がしてきたので、公式を見ることに。
factory_girl/GETTING_STARTED.md at master · thoughtbot/factory_girl
Sequences
Unique values in a specific format (for example, e-mail addresses) can be generated using sequences.
Sequences are defined by calling sequence in a definition block, and values in a sequence are generated by calling generate:# Defines a new sequence FactoryGirl.define do sequence :email do |n| "person#{n}@example.com" end end generate :email # => "person1@example.com" generate :email # => "person2@example.com"
ユニークな値を作るけど、rails c
でやると毎回1から始まるということなのだろうか。
rails c
でやると本当にDBに登録されてしまうから、何度もrails c
で起動してやるとキーが被ってエラーになると。
この書籍では、そもそもrails c
でFactoryGirl#create
を実行することは本筋ではない。
あくまで定義ファイルでレコード作るとこうなるよと提示したかったに過ぎないと思う。
この後のテストコードではこのエラーが出てても問題ないので、無視することとした。
(テストでは実際にDBにデータ作るわけではないからか、エラーは発生しない)
直すとしたら、完全に一意になるような仕組みにすべきだけど
そもそも、FactoryGirl#create
でテストデータを作ることはよくやるのだろうか?
(3) ビューのテストを実行すると、エラーになる
bundle exec rspec spec/views/events/show.html.erb_spec.rb
typoはないけど、なぜかエラーに。
Failures:
1) events/show 未ログインユーザがアクセスしたとき かつ、 @event.owner が nil のとき "退会したユーザです"と表示されていること
Failure/Error: allow(view).to receive(:logged_in?) { false }
# 中略
Finished in 0.18131 seconds (files took 3.79 seconds to load)
1 example, 1 failure
参考サイトのまま直したら通った。
# spec/views/events/show.html.erb_spec.rb
require 'rails_helper'
RSpec.describe "events/show", type: :view do
context '未ログインユーザがアクセスしたとき' do
before do
def view.logged_in?; end
def view.current_user; end
allow(view).to receive(:logged_in?) { false }
allow(view).to receive(:current_user) { nil }
end
# 中略
end
end
参考サイト
(tohashi's blog) 最新のrspec-railsでコントローラのヘルパーメソッドをスタブする
(4) JavaScriptのエンドツーエンドテストでのCapybara::ElementNotFound
の対処
bundle exec rspec spec/features/create_ticket_spec.rb
これもtypoがないけど、エラー。
Failures:
1) ユーザがイベント参加表明をする ログインユーザが、イベント詳細ページで"参加する"をクリックしたとき コメント入力用のモーダルウィンドウが表示されていること
Failure/Error: expect(page.find('#createTicket')).to be_visible
Capybara::ElementNotFound:
Unable to find css "#createTicket"
# ./spec/features/create_ticket_spec.rb:15:in `block (3 levels) in <top (required)>'
2) ユーザがイベント参加表明をする ログインユーザが、イベント詳細ページで"参加する"をクリックしたとき かつ、コメントを入力し"送信"ボタンをおしたとき "このイベントに参加表明しました"と表示されていること
Failure/Error: fill_in 'ticket_comment', with: '参加します!'
Capybara::ElementNotFound:
Unable to find field "ticket_comment"
# ./spec/features/create_ticket_spec.rb:20:in `block (4 levels) in <top (required)>'
Finished in 5.8 seconds (files took 3.07 seconds to load)
2 examples, 2 failures
エラー内容的には、HTMLエレメントが見つからないと言われているようで。
ググっても原因がわからない...
そもそもrspecやcapybaraを全然理解してなかったので、参考サイトで一通り確認。
原因探すのに便利そうなメソッドを見つけた。
(Qiita) フィーチャスペックでテスト失敗時に自動的に save_and_open_page を実行する方法
save_and_open_page はRSpecのフィーチャスペックの実行中に、その時点の画面の状態をブラウザで確認できるメソッドです。
使ってみて、ハードコピーを確認すると、どうも「Twitterでログイン」ボタンが押せてないようで。
試しに押した直後にsleep
入れたらうまくいった。
require 'rails_helper'
describe 'ユーザがイベント参加表明をする', js: true do
let!(:event) { create :event }
context 'ログインユーザが、イベント詳細ページで"参加する"をクリックしたとき' do
before do
visit root_path
click_link 'Twitterでログイン'
sleep 1
visit event_path(event)
click_on '参加する'
end
# 中略
end
end
自分の環境のスペック的な問題だったのだろうか。
とりあえず、これで。
参考サイト
(Qiita) 使えるRSpec入門・その4「どんなブラウザ操作も自由自在!逆引きCapybara大辞典」
(Qiita) フィーチャスペックでテスト失敗時に自動的に save_and_open_page を実行する方法