rspecの悪い書き方、良い書き方

## ケース1

### 悪い書き方: `it`に文脈を書いてしまっている。

```rb
describe '#hoge' do
  it '引数がfooの場合barを返す' do
    expect(hoge('foo')).to eq 'bar'
  end
end
```

### 良い書き方: `context`を使いましょう。`it`には期待値だけを書きましょう。

```rb
describe '#hoge' do
  context '引数がfooの場合' do
    let(:args) { 'foo' }
    it 'barを返す' do
      expect(hoge(args)).to eq 'bar'
    end
  end
end
```

## ケース2

### あまり良くない書き方: `its`を使っていない

```rb
describe '#hoge' do
  it { expecte(hoge).to eq 'bar' }
end
```

### 良い書き方: `its`を使いましょう。


```rb
its(:hoge) { is_expected.to eq 'bar' }
```

(`its`を使うためには`rspec-its` gemが必要です。)

## ケース3

### 悪い書き方: `context`の内容が直下の`before`の内容と違う。

```rb
context 'ログインしている場合' do
  it '名前が表示される' do
    login(user)
    is_expected.to have_content 'Alice'
  end
end
```

### 良い書き方: `before`を使って`context`の内容と`before`の内容が同じになるようにしましょう。

```rb
context 'ログインしている場合' do
  before { login(user) }
  it '名前が表示される' do
    is_expected.to have_content 'Alice'
  end
end
```

## ケース4

### 悪い書き方: `let`を使っていない。

```rb
describe User do
  before { @user = create(:user, name: 'Alice') }

  it '#name' do
    expect(@user.name).to eq 'Alice'
  end
end

```

### 良い書き方: `let`を使いましょう。

```rb
describe User do
  let(:user) { create(:user, name: 'Alice') }

  it '#name' do
    expect(user.name).to eq 'Alice'
  end
end
```

## ケース5

### 悪い書き方: `describe`と`subject`が一致しない(`subject`を使っていない)。

```rb
describe User do
  let(:user) { create(:user, name: 'Alice') }

  it '#name' do
    expect(user.name).to eq 'Alice'
  end
end
```

### 良い書き方: `describe`の内容と`subject`は一致させましょう。

```rb
describe User do
  let(:user) { create(:user, name: 'Alice') }
  subject { user }

  its(:name) { is_expected.to eq 'Alice' }
end
```

これで`its`も使えるようになります。

## ケース6

### 悪い書き方: factory_girlのリレーションを使っていない。

```rb:user_factory.rb
FactoryGirl.define do
  factory :user do
    company_id { rand(1..100) }
    name     { Faker::Name.name }
  end
end
```

```rb
let(:company) { create(:company) }
let(:user) { create(:user, company_id: company.id) }
```

### 良い書き方: リレーションを使いましょう。

```rb:user_factory.rb
FactoryGirl.define do
  factory :user do
    company
    name     { Faker::Name.name }
  end
end
```

```rb
let(:company) { create(:company) }
let(:user) { create(:user, company: company) }
```

## ケース7

### あまり良くない書き方: `shoulda`を使っていない。

```rb
context 'keyが空の場合' do
  before { user.key = '' }
  it { is_expected.to be_invalid }
end

```

### 良い書き方: `shoulda`を使いましょう。

```rb
it { is_expected.to validate_presence_of(:key) }
```

他に何ができるかは https://github.com/thoughtbot/shoulda-matchers を見てください。
かなり色々できます。


## ケース8

### 悪い書き方: `should`を使っている。

```rb
it { foo.should eq 'bar' }
```

### 良い書き方: `expect`を使いましょう。

```rb
it { expect(foo).to eq 'bar' }
```

## ケース9

### あまり良くない書き方: 重いテストをまとめていない。

```rb
RSpec.feature 'Project List Page', js: true do
  before { visit project_list_path }
  subject { page }

  it { is_expected.to have_content 'ID' }
  it { is_expected.to have_content '名前' }
  it { is_expected.to have_content '受注先会社名' }
  it { is_expected.to have_content '開始日' }
  it { is_expected.to have_content '終了日' }
  it { is_expected.to have_content '契約日' }
end
```

### 良い書き方: 重いテストは１つにまとめましょう

「１つのテストで確認することは１つだけ」は正しいことですが、テストの実行にかかる時間とトレードオフです。
仮想ブラウザを使ったテストなどの重いテストはまとめましょう。

```rb
RSpec.feature 'Project List Page', js: true do
  before { visit project_list_path }
  subject { page }

  it '各項目が存在すること' do
    is_expected.to have_content 'ID' 
    is_expected.to have_content '名前' 
    is_expected.to have_content '受注先会社名' 
    is_expected.to have_content '開始日' 
    is_expected.to have_content '終了日' 
    is_expected.to have_content '契約日' 
  end
end
```

#### aggregate_failuresの使用

上記の例について、aggregate_failuresで各expectを囲ってやることでテストがfailした時にどの項目で落ちたのかがわかるようになります。

```rb
RSpec.feature 'Project List Page', js: true do
  before { visit project_list_path }
  subject { page }

  it '各項目が存在すること' do
    aggregate_failures 'testing items' do
      is_expected.to have_content 'ID' 
      is_expected.to have_content '名前' 
      is_expected.to have_content '受注先会社名' 
      is_expected.to have_content '開始日' 
      is_expected.to have_content '終了日' 
      is_expected.to have_content '契約日' 
    end
  end
end
```

spec_helperに以下の記述を追加することでも同じことが可能です。

```rb
config.define_derived_metadata do |meta|
  meta[:aggregate_failures] = true unless meta.key?(:aggregate_failures)
end
```

## まとめ的な

specとは仕様という意味です。つまり、rspecは仕様書です。
その点をふまえて以下の点を気をつけるといいと思います。

- `context`,`describe`を正しく使って仕様書として読みやすくすること
- `context`,`describe`で謳っていることと、`before`,`subject`,`it`内のテストコードの内容が一致すること/一致することがすぐわかること

上記を守って楽しくプログラミング！