System specを使えるRuby on Rails開発環境をDockerで作る

Railsチュートリアルの2周目を、Docker環境でかつテストをRspecに代えて進めていたのですが、System specを導入しようとしたところで挫折…。
いろいろ調べたり、指導者の方に聞いたりして解決しましたので、同じように迷っている人の為になればと思い、書き残してみます。

## そもそもSystem specとはなんぞや？
System specについて知らない人はこちらの記事が参考になると思います。
[rspec-rails 3.7の新機能！System Specを使ってみた](https://qiita.com/jnchito/items/c7e6e7abf83598a6516d)

簡単に説明すると、エンドツーエンド（E2E）テストを実行するための機能です。みなさんが実際にブラウザを利用するようにボタンやリンクを押してどの画面に遷移しただとか、その画面にはある特定のHTMLタグが含まれているかどうかなどをテストすることができます。
使ってみればわかると思いますが、とてもわかりやすく便利です。

ちなみに、System specはrspec-rails 3.7以降から導入されたのですが、その前は**Feature spec**というのが使われていました。実はほとんど同じなので、Feature specで進めても良いのですが、[公式](http://rspec.info/blog/2017/10/rspec-3-7-has-been-released/)で推奨されているのでSystem specを使ってみたいと思います。

System(Feature) specで使えるいろいろな機能は以下の記事を参考にしてください。
[使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)

### さらにそもそもRSpecやDockerに詳しくない方へ
RSpec→[使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」](https://qiita.com/jnchito/items/42193d066bd61c740612)
Docker→[【図解】Dockerの全体像を理解する -前編-](https://qiita.com/kotaro-dr/items/b1024c7d200a75b992fc)
※ Dockerがインストールされていることを前提に話を進めますので、忘れずにインストールしておいてくだい。

では、本題に入りましょう。
ちなみに、一から作成していくので、すでにRailsでDocker環境を持っている人は適宜置き換えて試してみてください。

## docker-composeを用いてRails Projectを作成する
完成版をおいておきます。
https://github.com/tegnike/system_spec

ではまず、以下のファイルを作成しましょう。

- Dockerfile
- docker-compose.yml
- Gemfile
- Gemfile.lock

RailsのDocker環境作成に関しては以下の記事を参考にしています。
[docker-compose で Rails の開発環境を作る](https://qiita.com/skyriser/items/2cf98b747ed6577cc5ee)

```dockerfile:Dockerfile
FROM ruby:2.4.2
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client unzip
RUN sh -c 'wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -' && \
    sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' && \
    apt-get update && apt-get install -y google-chrome-stable
RUN mkdir /app

WORKDIR /tmp
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

ADD . /app
WORKDIR /app
```

System specではGoogle Chromをインストールしておく必要があるので、3行目の**RUN**でそれを実行しています。

```yaml:docker-compose.yml
db:
  image: postgres
  expose:
    - "5432"
web:
  build: .
  command: bundle exec rails s -p 3000 -b '0.0.0.0'
  volumes:
    - .:/app
  ports:
    - "3000:3000"
  links:
    - db
```

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.1.4'
```

```ruby:Gemfile.lock
```

**Gemfile.lock**は中身は空のまま、ファイルだけ作成ください。
ちなみに、Dockerfileの3行目の**RUN**でsystem specに必要なGoogle Chromeをwebコンテナにインストールしています。

これらのファイルが用意できたら、それらがあるフォルダで以下のコマンドを実行してください。

```
$ docker-compose run web rails new . --force --database=postgresql --skip-test
```

このコマンドを実行することで、docker-compose.ymlに定義したwebコンテナを一時起動し、Rails Projectを作成することができます。

フォルダ下にRailsのファイルたちが追加されていますね。
では、更新されたGemfileにrspecでのテストに必要な下記のgemを追記しましょう。

```ruby:Gemfile
gem 'therubyracer', platforms: :ruby # 先頭の"#"を消す

### 省略 ###

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console |
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] # 元から書いてあります
  gem 'rspec-rails' # 追記
end

group :test do
  gem 'capybara' # 追記
  gem 'selenium-webdriver' # 追記
  gem 'webdrivers' # 追記
  gem 'launchy', '~> 2.4.3' # 追記
end
```

では、更新したGemfileをimageに含めるためにbuildを実行しましょう。

```
$ docker-compose build
```

次に、**database.yml**を編集します。
今回はデータベースにpostgresqlを使用するので、以下のように設定してください。

```yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  username: postgres # 追記
  host: db # 追記
  password: # 追記
```

以下のコマンドでデータベースを作成します。

```
$ docker-compose run web rake db:create
```

これで一通り準備が整いましたので、下記のコマンドでコンテナを立ち上げてから `localhost:3000` にアクセスすれば、お馴染みの**Yay! You're on Rails!**を見ることができます。

```
$ docker-compose up -d
```

## System specを導入する
では、System specを導入していきましょう。
まず、下記のコマンドを実行することでRSpecの設定ファイルである `spec_helper.rb` と `rails_helper.rb` の2つのファイルが作成されます。

```
$ docker-compose run web rails generate rspec:install
```

そして `rails_helper.rb` は以下のように修正してください。

```rb:spec/rails_helper.rb
# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] = 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
require 'capybara/rspec'
# Add additional requires below this line. Rails is not loaded until this point!

# Requires supporting ruby files with custom matchers and macros, etc, in
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
# run as spec files by default. This means that files in spec/support that end
# in _spec.rb will both be required and run as specs, causing the specs to be
# run twice. It is recommended that you do not name files matching this glob to
# end with _spec.rb. You can configure this pattern with the --pattern
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
#
# The following line is provided for convenience purposes. It has the downside
# of increasing the boot-up time by auto-requiring all files in the support
# directory. Alternatively, in the individual `*_spec.rb` files, manually
# require only the support files necessary.
#
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your
  # examples within a transaction, remove the following line or assign false
  # instead of true.
  config.use_transactional_fixtures = true

  # RSpec Rails can automatically mix in different behaviours to your tests
  # based on their file location, for example enabling you to call `get` and
  # `post` in specs under `spec/controllers`.
  #
  # You can disable this behaviour by removing the line below, and instead
  # explicitly tag your specs with their type, e.g.:
  #
  #     RSpec.describe UsersController, :type => :controller do
  #       # ...
  #     end
  #
  # The different available types are documented in the features, such as in
  # https://relishapp.com/rspec/rspec-rails/docs
  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
  config.before(:each, type: :system) do
    driven_by :selenium_chrome
  end

  config.before(:each, type: :system, js: true) do
    driven_by :selenium_chrome
  end
end
```

もう1つ、 `capybara.rb` というファイルを作ります。これはまだ作成されていないので、specフォルダの下にsupportファイルを作り、その下に配置しましょう。

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

Capybara.register_driver :selenium_chrome do |app|
  options = ::Selenium::WebDriver::Chrome::Options.new

  options.add_argument('--headless')
  options.add_argument('--no-sandbox')
  options.add_argument('--disable-gpu')
  options.add_argument('--window-size=1400,1400')

  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
end
```

これでSystem specの設定は完了です。

## System specでテストしてみよう

せっかく作ったので試してみたいのですが、今のままだとモデルがなくてテストができないので、 `scaffold` コマンドでUserモデルを作成します。ついでにマイグレーションも実行しましょう。

※ ちなみに今回はSystem specが動くかどうか確認することを目的としていますので、複雑なテストは書きません。

```
$ docker-compose run web bin/rails g scaffold User name:string
$ docker-compose run web rake db:migrate
```

specフォルダ下にSystem spec用のフォルダを作成し、そこに `users_spec.rb` を作成します。

```ruby:spec/system/users_spec.rb
require 'rails_helper'

RSpec.describe 'Users', type: :system, js: true do
  before do
    @user = User.create!(name: 'Test User')
  end

  it 'ユーザ名が表示されること' do
    visit user_path(@user)

    expect(page).to have_content @user.name
  end
end
```

では、テストを実行してみましょう。

```
$ docker-compose up
$ docker-compose run web rspec spec/system/users_spec.rb
Starting system_spec_db_1 ... done
.

Finished in 5.18 seconds (files took 4.95 seconds to load)
1 example, 0 failures
```

はい、問題なく成功しました！

## まとめ
この記事では、System Specを使えるDocker環境の構築を説明しました。
冒頭でも説明しましたが、Feature Specとほとんど一緒のSystem Specですが、確実に上位互換なので導入しておいて損はないでしょう（公式でも推奨されていますしね）。

では、また。

## 参考
- [rspec-rails 3.7の新機能！System Specを使ってみた](https://qiita.com/jnchito/items/c7e6e7abf83598a6516d)
- [使えるRSpec入門・その4「どんなブラウザ操作も自由自在！逆引きCapybara大辞典」](https://qiita.com/jnchito/items/607f956263c38a5fec24)
- [docker-compose で Rails の開発環境を作る](https://qiita.com/skyriser/items/2cf98b747ed6577cc5ee)