rails、君に決めた！！~13

一連の記事の目次
[rails、君に決めた！！~目次](https://qiita.com/satousatou/items/d20b61c198a4d439f2e7)


## テスト
### 実行環境の構築
サンプル用アプリを作る

```bash
mkdir rspec-sample
cd rspec-sample
bundle init
```

Gemfileの`gem "rails"`のコメントアウトを外し、

```bash
bundle install --path vendor/bundle --jobs=4
bundle exec rails new ./ -d mysql --skip-turbolinks --skip-sprockets --skip-test
```

次にデータベースを用意する

```bash
mysql.server start
bin/rake db:create RAILS_ENV=test
```

データベースが作成されているか確認

```bash
bin/rake db:create RAILS_ENV=test

mysql -u root

mysql> show databases;
+------------------------+
| Database               |
+------------------------+
| information_schema     |
| mysql                  |
| performance_schema     |
| rails_app              |
| rails_app5_development |
| rails_app5_test        |
| rails_app_test         |
| rspec-sample_test      |
| sys                    |
+------------------------+
9 rows in set (0.01 sec)

mysql> exit
Bye
```

テスト用のgemのインストール

```ruby
group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'database_cleaner'
  gem 'faker'
  gem 'pry-rails'
  gem 'pry-coolline'
end
```

ここ、間違えて`group :development`に書かないように注意！

bundle install

次にRspecのインストールと使用準備

```bash
bin/rails g rspec:install

Running via Spring preloader in process 49994
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb
```

railsに関する設定: rails_helper.rb
rspecに関する設定: spec_helper.rb

DatabaseCleanerを設定する。
テストする前のデータベースの状態が常に同じになるようにする？
テストすると元のデータが変更されてしまうから元の状態に戻したいよねってことかな
> テストは意図しない結果にならないように、そのテストに必要最低限なデータだけを準備して実行する必要がある。 
例えば、あるテストケースを実行する際に他のテストケースで生成されたデータの影響を受けてテストケースが失敗するということを避けなければならない。
database_cleanerを利用すれば、テスト前にDBを初期化・テスト後にテストで作ったデータの後始末を実行することができて、テストの独立性を担保することができる。

fmfm

spec/rails_helper.rb

```ruby
RSpec.configure do |config|
  # テストを開始する際の処理を記述
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  # サンプル実行前後の処理を記述
  config.around(:each) do |example|
    DatabaseCleaner.cleaning do
      example.run
    end
  end
end
```


次にFactoryBotを設定
よくわからないけどテストデータを簡単に生成してくれるんだと。

spec/rails_helper.rb

```ruby
Rspec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end
```

これでRspecの準備は完了

### テストを書く
まずテストのための簡単なモデルを作る

```bash
bin/rails g model Article title:text body:text status:integer published_at:datetime

# migrationを実行
bundle exec rake db:migrate RAILS_ENV=test
```

モデルやマイグレーションファイルの他に、spec/models/article_spec.rbとspec/factories/article.rbが作られる

spec/models/article_spec.rb

```ruby
require 'rails_helper'

RSpec.describe Article, type: :model do
  pending "add some examples to (or delete) #{__FILE__}"
end
```

Articleモデルのテストケースを追記していくファイル（スペックと呼ぶ）

spec/factories/articles.rb

```ruby
FactoryBot.define do
  factory :article do
    title "MyText"
    body "MyText"
    status 1
    published_at "2018-03-26 18:42:13"
  end
end
```

FactoryBotの動作確認してみよー

```bash
bin/rails c test --sandbox

[1] pry(main)> FactoryBot.create(:article)                                                                                                       
 
=> #<Article:0x007fb089158d08
 id: 1,
 title: "MyText",
 body: "MyText",
 status: 1,
 published_at: Mon, 26 Mar 2018 18:42:13 UTC +00:00,
 created_at: Mon, 26 Mar 2018 12:40:31 UTC +00:00,
 updated_at: Mon, 26 Mar 2018 12:40:31 UTC +00:00>
```

よっしゃ〜

最後にArticleモデルに下記の変更を加える
app/models/article.rb

```ruby
class Article < ApplicationRecord
  enum status: {draft: 0, published: 1}

  def abbreviated_title
    title.size >= 20 ? "#{title.slice(0, 19)}..." :title
  end

  def publish
    return if self.published?
    update({status: Article.statuses['published'], published_at: Time.current})
  end 
end
```

> abbreviate: 短縮する

### Rspecの基本文法
Rspec内で利用できるメソッドをいくつか見ていく
#### it
> サンプルを定義する

テストのサンプルを定義できるメソッド。実際に書いてみたほうがわかりやすい
spec/models/article_spec.rb
(ここ本ではapp/models/article.rbに書けって書いてあるけど誤植かな？ p193)

```ruby
RSpec.describe Article, type: :model do
  it '記事タイトルがそのまま帰ること' do
    article = Article.new(title: 'タイトルだよ')
    expect(article.abbreviated_title).to eq 'タイトルだよ'
  end

  it '記事が公開状態になること' do
    article = Article.new(status: :draft)
    article.publish
    expect(article.published?).to be_truthy
  end
end
```

#### describe
> あるサンプルが、どのテストの対象なのかを明らかにする

```ruby
RSpec.describe Article, type: :model do
  describe '.abbreviated_title' do
    it '記事タイトルがそのまま帰ること' do
      article = Article.new(title: 'タイトルだよ')
      expect(article.abbreviated_title).to eq 'タイトルだよ'
    end
  end

  describe '.publish' do
    it '記事が公開状態になること' do
      article = Article.new(status: :draft)
      article.publish
      expect(article.published?).to be_truthy
    end
  end
end
```

#### context
> サンプルケースの状況を細かく説明する

テストのサンプルが失敗した時に、ログがわかりやすくなる

```ruby
RSpec.describe Article, type: :model do
  describe '.abbreviated_title' do
    context '記事タイトルが20文字未満の場合' do
      it '記事タイトルがそのまま帰ること' do
        article = Article.new(title: 'タイトルだよ')
        expect(article.abbreviated_title).to eq 'タイトルだよ'
      end
    end

    context '記事タイトルが20文字以上の場合' do
      it '記事タイトルが省略されること' do
        article = Article.new(title: 'a' *20)
        expect(article.abbreviated_title).to eq "#{'a' *19}..."
      end
    end
  end

  describe '.publish' do
    context '記事が非公開状態の場合' do
      it '記事が公開状態になること' do
        article = Article.new(status: :draft)
        article.publish
        expect(article.published?).to be_truthy
      end
    end

    context '記事が公開状態の場合' do
      it '記事が公開状態のままであること' do
        article = Article.new(status: :published)
        article.publish
        expect(article.published?).to be_truthy
      end
    end
  end
end
```

#### matcher(マッチャ)
`expect(article.abbreviated_title).to eq 'タイトル'`
expectメソッドの後にチェインしているtoメソッドの第一引数に指定されているもの。
上の例では`eq`

#### フック
> テスト前後で実行しておきたい処理を定義できる仕組み

```ruby
before(:each) { @article = Article.new }

before(:each) do
  @article = Article.new
end
```

> 例えば、テスト実行前にデータベースを掃除する処理で使える

え、DatabaseCleanerでやるんじゃなかったの？？
違いがよくわからないw

> インスタンス変数を定義することで、サンプル間で同じオブジェクトを参照しあうことが可能

全然わからぬ〜。テストAとテストBで同じオブジェクトを利用できるってこと？

```ruby
before(:each) { @article = Article.new }

before(:each) do
  @article = Article.new
end
```

フック

```
before
after
around
```

フックスコープ

```
each,example
all, context
suite
```

#### pending, skip
> pending:未決定の、〜中の

英語の勉強になる笑

> pending:どうしても落ちてしまうテストを保留する
skip:実装途中のサンプルなどに対して一時的なスキップを適用する

どういうシチュエーションで使うのかいまいちイメージできないけど、
`expect(1).to eq 2`みたいに当然落ちるテストを保留にできるのがpending

skipは、pendingと違いコールされたところで処理が中断され、結果に保留が表示される。

skipはわかる、ちょっと保留したい時便利。pendingはよくわからないw。いつ使うの？？

### モデル以外のテスト
#### コントローラのテスト
マイページにはログイン状態でナイトアクセスできない、というのをテストするとき、

```ruby
RSpec.describe MyPageController, type: :controller do
  describe 'GET #index' do
    context 'ログイン状態の場合' do
      it 'マイページが表示されること' do
        login
        get :index
        expect(response.status).to render_template('mypage/index')
      end
    end

    context '未ログイン状態の場合' do
      it 'トップページにリダイレクトされること' do
        get :index
        expect(response).to redirect_to root_path
      end
    end
  end
end
```

#### ヘルパーのテスト

```ruby
RSpec.describe ApplicationHelper, type: :helper do
  describe '#image_with_size' do
    it 'GETクエリに指定パラメータが付与されたURLが返ること' do
      expect(helper.image_with_size('http://example.com/hoge.jpg', 30)).to eq('http://example.com/hoge.jpg?size=30')
    end
  end
end
```