4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

パーフェクト Ruby on Rails 第7章 ハマったところ

Last updated at Posted at 2016-01-05

パーフェクト 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 cFactoryGirl.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 cFactoryGirl#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 を実行する方法

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?