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

パーフェクト Ruby on Rails 第７章のRailsアプリケーションのテストで
何箇所かハマったところがあったので、対処方法まとめときます。
ただし、その場凌ぎの対処ばかりで**根本的な対処方法ではない**と思いますのでご注意ください。

また、環境は必ずしも書籍通りになっていません。
（なるべく最新のを入れました）

* Rails 4.2.4
* RSpec 3.4.0

# (0) 前書き

書籍では、*RSpec 3.0.0.beta2* を使用しているようです。
ハマったわけではないですが、`% ./bin/rails g rspec:model event`を行った時に生成される雛形がそもそも違いました。

``` ruby
# 書籍だと...
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 が入った
```

``` ruby
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'などと出たので。](http://qiita.com/noriyotcp/items/83aeb0cdcb834d42c955)

# (2) `rails c`で`FactoryGirl.create(:event)`を実行すると失敗する

`./bin/rails c`を実行し、`FactoryGirl.create(:event)`を実行してみますと書いてあったので実行した。
最初は問題なく成功。

ただ何度かやってるとエラーが発生するようになった。

``` ruby
[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モデルの登録しようとしているレコードがユニークじゃないよといった感じか。

マイグレーション用ファイルでは、

``` ruby
# db/migrate/XXXXXXXXXXXXXXX_create_users.rb
add_index :users, [:provider, :uid], unique: true
```

と指定してて、factory_girlのUserモデル用の定義ファイルでは、

``` ruby
# 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](https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md)

> # 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:
> ``` ruby
> # 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`でやると毎回１から始まるということなのだろうか。
`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はないけど、なぜかエラーに。

``` ruby
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

```
参考サイトのまま直したら通った。

``` ruby
# 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でコントローラのヘルパーメソッドをスタブする](http://64.hateblo.jp/entry/2014/11/29/235823)

# (4) JavaScriptのエンドツーエンドテストでの`Capybara::ElementNotFound`の対処

```
bundle exec rspec spec/features/create_ticket_spec.rb
```

これもtypoがないけど、エラー。

``` ruby
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 を実行する方法](http://qiita.com/jnchito/items/6b3e1ab9baf692b986df)
> save_and_open_page はRSpecのフィーチャスペックの実行中に、その時点の画面の状態をブラウザで確認できるメソッドです。

使ってみて、ハードコピーを確認すると、どうも「Twitterでログイン」ボタンが押せてないようで。
試しに押した直後に`sleep`入れたらうまくいった。

``` ruby
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大辞典」](http://qiita.com/jnchito/items/607f956263c38a5fec24)  
[(Qiita) フィーチャスペックでテスト失敗時に自動的に save_and_open_page を実行する方法](http://qiita.com/jnchito/items/6b3e1ab9baf692b986df)