【RSpec】System Specが動作するRails+Docker開発環境を構築する

# はじめに

Rspec3.7以降で実装された"System Spec"を動作できるRails+Docker環境の作り方をまとめました。

基本コピペで進められるように書きましたので、初学者の方であっても「まず動く」状態まで持っていけると思います。

この記事では、Docker、docker-composeがすでにインストールされた状態からスタートします。私は、自PC(Mac)のホストOSに「[Docker for Mac](https://hub.docker.com/editions/community/docker-ce-desktop-mac)」をインストールしています。

著者動作環境における、Docker、docker-composeのバージョンは以下の通りです。

```:
$ docker --version
Docker version 19.03.5, build 633a0ea
```

```:
$ docker-compose -v
docker-compose version 1.25.4, build 8d51620a
```

# DockerでのRails環境の構築

まずは、Docker公式が用意してくれているRals用のクイックスタート「[Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)」をベースに、Railsが動作するDocker環境を作成していきます。

一部、公式のファイル内容に変更を加えていますので、ご注意ください。

早速始めていきましょう。作業ディレクトリ用意し、cdで移動します。

```:
$ mkdir sample_app
$ cd sample_app
```

Quickstartに則り、sample_app内に下記5ファイルを作成します。

```:/sample_app
$ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock entrypoint.sh
```


```:ディレクトリ構造
/sample_app
 ├── Dockerfile
 ├── Gemfile
 ├── Gemfile.lock
 ├── entrypoint.sh
 └── docker-compose.yml
  
```

各ファイルを下記の通り編集します。5ファイルのうち、docker-compose.yml以外は、Quickstart公式のコピペです。vim等、お好きなテキストエディタをお使いください。


```Dockerfile:Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```



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

```ruby:Gemfile.lock
(空のまま)
```

```sh:entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```

```yml:docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      - "POSTGRES_USER=xxxx" # 追記
      - "POSTGRES_PASSWORD=xxxx" # 追記
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
    environment:
      - "SELENIUM_DRIVER_URL=http://selenium_chrome:4444/wd/hub" # 追記
  selenium_chrome:
    image: selenium/standalone-chrome-debug # 追記
```

上記5ファイルのうち、docker-compose.ymlのみ、編集で手を加えています。具体的には、
・postgresにユーザー名、パスワードを指定
・System Specを動作させるためのコンテナイメージ「selenium/standalone-chrome-debug」を追加


ファイルを作成したら、Railsアプリを新規作成します。

```:
$ docker-compose run web rails new . --force --no-deps --database=postgresql
```

```:/sample_app
$ ls
Dockerfile              README.md               bin                     db                      lib                     public                  tmp
Gemfile                 Rakefile                config                  docker-compose.yml      log                     storage                 vendor
Gemfile.lock            app                     config.ru               entrypoint.sh           package.json            test
```

rails newに伴い、Gemfileにデフォルトのgemがインストールされました。

Gemfileに編集を加えます。具体的には、

・"rspec-rails"を追加
・"chromedriver-helper"を削除

```ruby:Gemfile

# 省略

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
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of chromedriver to run system tests with Chrome
  # gem 'chromedriver-helper'　# 削除
end

#省略

```

Gemfileを更新したため、再ビルドします。

```:
$ docker-compose build
```

これで、新しいGemfileでbundle installが実行されました。

続いて、configディレクトリ内のdatabase.ymlを編集します。


```yml:config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: xxxx
  password: xxxx
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test
```

DBを作成します。

```:
$ docker-compose run web rails db:create
Starting sample_app_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
```

コンテナを起動します。このタイミングで、selenium/standalone-chrome-debugのプルが実行されます。

```:
$ docker-compose up -d
Pulling selenium_chrome (selenium/standalone-chrome-debug:)...
latest: Pulling from selenium/standalone-chrome-debug
.
.
.
```

イメージプルが終了しましたら、docker-composeが正常に立ち上がっているか、確認してみましょう。

```:
$ docker-compose ps
            Name                           Command               State           Ports         
-----------------------------------------------------------------------------------------------
sample_app_db_1                docker-entrypoint.sh postgres    Up      5432/tcp              
sample_app_selenium_chrome_1   /opt/bin/entry_point.sh          Up      4444/tcp, 5900/tcp    
sample_app_web_1               entrypoint.sh bash -c rm - ...   Up      0.0.0.0:3000->3000/tcp
```

web,db,selenium_chromeの3つが立ち上がっていれば、OKです。ついでに、http://localhost:3000 にアクセスし、いつもの「Yay! You’re on Rails!」が表示されているか、確認しましょう。

また、RSpecが正確にインストールされているかも確認してみましょう。

```:
$ docker-compose run web rspec -v
Starting sampleApp_db_1 ... done
RSpec 3.9
  - rspec-core 3.9.1
  - rspec-expectations 3.9.0
  - rspec-mocks 3.9.1
  - rspec-rails 3.9.0
  - rspec-support 3.9.2
```

各バージョンは状況により異なるかと思いますが、RSpec 3.7以上のバージョンであれば、System Specを動作させることができます。

# RSpecの初期設定
無事、必要なgemが揃った状態のDockerコンテナを用意できたので、ここからは中身の諸々の設定をいじっていきます。

まず、Railsにデフォルトで配置されているtestディレクトリは使用しないため、削除してしまいましょう。

```:
$ rm -rf test
```

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


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

これにより、以下のディレクトリ構造に対して、計3ファイルが生成・配置されます。

```:ディレクトリ構造
/sample_app
  ├── .rspec
  └── spec
       ├── rails_helper.rb
       └── spec_helper.rb
```

.rpsecファイルを編集します。2行目のコードを追記してください。これにより、テスト結果の表示形式が変更されます。


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


デフォルトでは、”失敗したテスト”のみがターミナル上に出力されるのですが、この変更を行うことにより、”成功・失敗両方のテスト”の結果を出力してくれるようになります。必須ではありませんが、設定しておくことをオススメします。


続いて、spec/rails_helper.rbについて、25行目あたりに書かれている下記コードのコメントアウトを外してください。

```ruby:spec/rails_helper.rb
# 省略

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

# 省略
```

これにより、後ほど定義するCapybara用の設定ファイルを読み込めるようになります。

## specファイルの自動生成機能OFF

デフォルトでは、controllerやviewを作成したタイミングで、対応するspecファイル（＝テストコードを書くためのファイル）が自動生成されます。

今回は初学者にとっての混乱を防ぐため、自動生成はOFFにしておきます。config/application.rbに下記コードを追加します。

```ruby:config/application.rb
equire_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Myapp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
    
    ### 追記 ######################################
    config.generators do |g|
      g.test_framework :rspec,
                       view_specs: false,
                       helper_specs: false,
                       controller_specs: false,
                       routing_specs: false,
                       request_specs: false
    end
    ##############################################
  end
end
```

## Capybaraの初期設定

Capybaraに設定を加えるため、capybara.rbファイルを作ります。

```:
$ mkdir spec/support
$ touch spec/support/capybara.rb
```


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

RSpec.configure do |config|
  config.before(:each, type: :system) do
    driven_by :selenium, using: :headless_chrome, options: {
      browser: :remote,
      url: ENV.fetch("SELENIUM_DRIVER_URL"),
      desired_capabilities: :chrome
    }
    Capybara.server_host = 'web'
    Capybara.app_host='http://web'
  end
end
```

これで、SystemSpecが動作する環境が整いました。

# System Spec実践

それでは実際に、静的ページを用いてSystemSpecを使用してみましょう。

今回は、

・rootページにアクセスし、"Hello World!"が表示されていることを検証
・rootページに、"Helpページ"へのリンクが置かれていることを検証
・リンク先のHelpページに、"This is the help page."が表示されていることを検証

という流れのテストを書いてみたいと思います。

下準備として、home,helpを持ったStaticPagesコントローラーを作成します。

```:
$ docker-compose run web rails generate controller StaticPages home help
```

ルートとビューをいじります。

```ruby:config/routes.rb
Rails.application.routes.draw do
  root 'static_pages#home'
   get '/help', to: 'static_pages#help'
end
```

```erb:app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p><%= link_to "Help", help_path %></p>
```

```erb:app/views/static_pages/help.html.erb
<h1>This is the help page.</h1>
```


下準備が終わったため、specファイルを書いていきます。specディレクトリ下にsystemディレクトリを作成し、その中に"homes_spec.rb"を配置します。

```:
$ mkdir spec/system
$ touch spec/system/homes_spec.rb
```

spec/system/homes_spec.rbを編集します。


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

RSpec.describe 'Home', type: :system do
  it 'shows greeting' do
    # root_pathへアクセス
    visit root_path
    # ページ内に'Hello World!'が含まれているかを検証
    expect(page).to have_content 'Hello World!'
    # 'Help'文字列をクリック
    click_on 'Help'
    # ページ内に'This is the help page.'が含まれているかを検証
    expect(page).to have_content 'This is the help page.'
  end
end
```

いよいよ動かします！今回用意した環境でSystem Specを動かすためには、webコンテナ内に入る必要があります。

```:
$ docker-compose exec web bash
```


rspecは、"rails spec"コマンドで実行できます。


```:webコンテナ内
$ rails spec
/usr/local/bin/ruby -I/usr/local/bundle/gems/rspec-core-3.9.1/lib:/usr/local/bundle/gems/rspec-support-3.9.2/lib /usr/local/bundle/gems/rspec-core-3.9.1/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb

Home
Capybara starting Puma...
* Version 3.12.4 , codename: Llamas in Pajamas
* Min threads: 0, max threads: 4
* Listening on tcp://web:45873
  shows greeting

Finished in 2.33 seconds (files took 8.35 seconds to load)
1 example, 0 failures
```

無事、テストが通ればOKです！！

# スクリーンショット保存機能

ちなみにですが、System Specには、「失敗時の画面をスクリーンショットで自動保存する」という便利機能があります。

ビューをいじって、わざとテストを失敗させてみましょう。

```erb:app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p><%= link_to "Hel", help_path %></p>  # "Help"をタイポしてしまったケース
```

```:webコンテナ内
$ rails spec

.
.
.

Home
Capybara starting Puma...
* Version 3.12.4 , codename: Llamas in Pajamas
* Min threads: 0, max threads: 4
* Listening on tcp://web:44173
  shows greeting (FAILED - 1)

Failures:

  1) Home shows greeting
     Failure/Error: click_on 'Help'
     
     Capybara::ElementNotFound:
       Unable to find link or button "Help"
     
     [Screenshot]: tmp/screenshots/failures_r_spec_example_groups_home_shows_greeting_612.png
.
.
.
```

「'Help'が見つかりません！」とメッセージが出され、tmp/screenshotsにエラーが発生したタイミングでのブラウザ画面が保存されています。


![failures_r_spec_example_groups_home_shows_greeting_612.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/540216/2bff4460-3692-e2bc-128e-c53b236ea969.png)

"Help"と書いたつもりのところが"Hel"になってしまっているのが、確認できます。

## 最後に

以上です！不明な点、間違っている点などがありましたら、私のTwitter([@ddpmntcpbr](https://twitter.com/ddpmntcpbr))のDMまで連絡頂けると幸いです。