基礎からやり直す Rails RSpec

RSpecについてちゃんと学習してこなかったので、今回しっかり腰を据えて復習しました。

使った教材は次の3冊です。

- [Everyday Rails - RSpecによるRailsテスト入門](https://leanpub.com/everydayrailsrspec-jp)
- [Effective Testing with RSpec 3: Build Ruby Apps with Confidence](https://amzn.to/2HiMjZW)
- [Rails Testing Handbook](https://semaphoreci.com/ebooks/rails-testing-handbook)

以下では、そこで学んだことを整理します。

# RSpecのテスト設計

RSpec の基本的なディレクトリ構造は公式ドキュメントで示されています。

[公式ドキュメント](https://relishapp.com/rspec/rspec-rails/docs/directory-structure)

基本的には次のような構成になります。

```
spec
├── factories        # テスト生成
├── features         # インテグレーション/アクセプタンス
├── helpers          # ユニット
├── models           # ユニット
├── rails_helper.rb  # 設定ファイル
├── requests         # インテグレーション/アクセプタンス
├── services         # ユニット
└── spec_helper.rb   # 設定ファイル
```

最近は Controller specs の代わりに、より広範囲をテストする Request specs が用いられるのが主流です（両者の違いについては[こちら](https://stackoverflow.com/questions/40851705/controller-specs-vs-request-specs)）。

RSpec チームからも Request specs を使うことが推奨されています（2016年：詳細は[こちら](http://rspec.info/blog/2016/07/rspec-3-5-has-been-released/)）。 

> For new Rails apps: we don't recommend adding the rails-controller-testing gem to your application. The official recommendation of the Rails team and the RSpec core team is to write request specs instead. 

また、ビジネスロジックをサービスオブジェクトに切り出す場合などは、他のユニットテストと同様に、`rspec/services` 配下にテストコードを書いていきます。


# RSpecの初期設定

新規にRailsアプリを作る場合の初期設定について整理します。

## gem インストール

まずは RSpec のインストールです。

```ruby:Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.6.0'
end
```

```
$ bundle install
```

次のコマンドで RSpec のファイルがもろもろ生成されます。

```
$ rails g rspec:install
```

## .rspec

好みですが、見やすいフォーマットで表示させるために .rspec ファイルを次のように編集します。

```:.rspec
--require spec_helper
--format documentation
```

## Spring

Springを使ってテスト環境の立ち上げ速度を上げます。

gem をインストールします。

```ruby:Gemfile
group :development do
  gem 'spring-commands-rspec'
end
```

```
$ bundle install 
```

binstub を使えるようにします。

```
$ bundle exec spring binstub rspec
```

これで次のコマンドで RSpec を実行できるようになりました。

```
$ bin/rspec
```

## config/application.rb

rails generate コマンドで自動生成されるファイル種類を調整します。

```ruby:config/application.rb
config.generators do |g|
  g.test_framework :rspec,
    view_specs: false,
    helper_specs: false,
    routing_specs: false
end
```

## spec/rails_helper.rb

次の行のコメントアウトを取り、spec/support 配下にあるRSpecの設定ファイルが読み込まれるようにします。

```ruby:spec/rails_helper.rb
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
```

## FactoryBot

テストデータ生成用のツール FactoryBot をインストールします。

```ruby:Gemfile
group :development, :test do
  gem 'factory_bot_rails', '~> 4.10.0'
end
```

```
$ bundle install
```

FactoryBotのコードを spec/factories 配下に書いていきます。

テストコードから `FactoryBot` を省略してデータ生成できるように設定ファイルを追加します。

```ruby:spec/support/factory_bot.rb
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
```

これで `FactoryBot.create(:user)` ではなく、 `create(:user)` と書くだけでデータ生成できるようになりました。

## ダミーデータ生成用の設定

テストデータ生成用のツール [Faker](https://github.com/faker-ruby/faker) と [ForgeryJa](https://github.com/namakesugi/forgery_ja) をインストールします。

```ruby:Gemfile
group :development, :test do
  gem 'faker', git: 'https://github.com/stympy/faker.git', branch: 'master'
  gem 'forgery_ja'
end
```


```
$ bundle install
```

これらを使えば、FactoryBot で任意のダミーデータをセットできます。（Faker は英数字用、ForgeryJa は日本語用です。）

```ruby:spec/factories/some_model.rb
require 'faker'
require 'forgery_ja'

FactoryBot.define do
  factory :some_model do
    name { ForgeryJa(:name).full_name }
    name_kana { ForgeryJa(:name).full_name(to: ForgeryJa::KANA) }
    zip_code { Faker::Number.number(digits: 7).to_s }
    code { Faker::Lorem.characters(number: 20) }
    sex { Faker::Number.between(from: 0, to: 2) }
    birthday { Faker::Date.birthday(min_age: 18, max_age: 65) 
    phone { Faker::Number.number(digits: Faker::Number.between(from: 10, to: 11)) }
    email { ForgeryJa('email').address }
  end
end
```

## Devise用の設定

Requset specs でログイン周りのDeviseヘルパーが利用できるように、設定ファイルを追加します。

```ruby:spec/support/request_spec_helper.rb
module RequestSpecHelper
  include Warden::Test::Helpers

  def self.included(base)
    base.before(:each) { Warden.test_mode! }
    base.after(:each) { Warden.test_reset! }
  end

  def sign_in(resource)
    login_as(resource, scope: warden_scope(resource))
  end

  def sign_out(resource)
    logout(warden_scope(resource))
  end

  private

  def warden_scope(resource)
    resource.class.name.underscore.to_sym
  end
end

RSpec.configure do |config|
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include RequestSpecHelper, type: :request
end
```

## Capybara

Feature specs 用のツールとして Capybara をインストールします。

```ruby:Gemfile
group :test do
  gem 'capybara', '~> 2.15.4'
  gem 'webdrivers'
  gem 'launchy', '~> 2.4.3'
end
```

`webdrivers` は、JSの挙動を含めたテストをする際に必要です。`launchy` は Feature specs のデバッグツールで、テストが失敗した場合にブラウザがどのような状態だったか、教えてくれます。

```
$ bundle install
```

Capybara が読み込まれるように設定ファイルを編集します。


```ruby:spec/rails_helper.rb
# Add additional requires below this line. Rails is not loaded until this point!
require 'capybara/rspec'
```

Capybara で利用するウェブドライバーを指定します。

```ruby:spec/support/capybara.rb
Capybara.javascript_driver = :selenium_chrome
```

ヘッドレスブラウザとして動かしたい場合は、次のように書きます。

```ruby:spec/support/capybara.rb
require 'capybara/rspec'
require 'selenium-webdriver'

Capybara.register_driver :headless_chrome do |app|
  opts = {
    browser: :chrome,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.chrome(
      chrome_options: { args: %w[headless disable-gpu window-size=1680,1050 start-maximized] }
    )
  }
  Capybara::Selenium::Driver.new(app, opts)
end

Capybara.javascript_driver = :headless_chrome
```

# 基本的なコマンド

RSpecの基本的なコマンドは次のとおりです。

## 全テストの実行

```
$ bin/rspec
```

## 特定ファイルのテスト

```
$ bin/rspec spec/unit 
```

## 処理速度の遅いテストの検出

```
$ bin/rspec —-profile 2
```

## キーワードが含まれるテストのみ実行

```
$ bin/rspec -e milk -fd
```

`-e: example`
`-fd: full_description`
* case-sensitive

## 失敗したテストだけ再実行

```
$ bin/rspec —-only-failure
```

# 便利なタグ

RSpec のテストコードで便利なタグを整理します。

## aggregate_failures

デフォルトでは、テスト内でエラーが1つ発生すると、後続のエラーはメッセージに表示されません。

`:aggregate_failures` タグをつけると、発生するエラーを全て同時に確認できます。

```ruby
RSpec.describe Ledger, :aggregate_failures do
  # your test code
end
```

このタグを使ってアサーションをネストさせることもできます。

```ruby
describe 'GET /your_root' do
  it 'responds successfully' do
    get your_root_path
    aggregate_failures do
      expect(response).to be_success
      expect(response).to have_http_status '200'
    end
  end
end
```

## focus

次のコマンドでテストを実行時すると `focus: true` タグがついてるテストのみ実行されます。

```ruby
describe 'SOME TEST', focus: true do
  it 'does something' do
    expect(1).to eq(1)
  end
end
```

```
$ bin/rspec --tag focus
```

各項目の頭に `f` をつけることでも、`focus: true` を指定できます。例えば、`fcontext`   `fit`  `fdescribe` のような具合です。
  
```ruby
fdescribe 'SOME TEST' do
  it 'does something' do
    expect(1).to eq(1)
  end
end
```

また、設定ファイル次のようにいじれば、`bin/rspec` コマンドだけで `focus: true` のみ（なければ全テスト）が実行されるようになります。

```ruby:spec_helper.rb
RSpec.configure  do  |config|
  config.filter_run_when_matching( focus:  true )
end
```