Rspecつまづきポイント！

この記事は[Ateam Hikkoshi Samurai Inc. & Ateam Connect Inc. Advent Calendar 2019](https://qiita.com/advent-calendar/2019/hikkoshi) 20日目の記事です。

# はじめに
ここ一年ほど、社内で自動テスト推進係をしております@ysysysysがお送りいたします。
社内の各プロジェクト、特にRailsプロジェクトでRspecを書きましょうと皆さんとともに日々頑張っております。
今回はRspecを書いていく中で個人的につまづきポイントだったなと思うところをいくつかピックアップしてその対応をご紹介しようかと思います。

# その１ let と let!
Rspecではletという機能があります。Spec内ではインスタンス変数で定義するよりもlet(:hoge)と定義するのが一般的なようです。
letをつかえば、その内容はメモリーされ、exampleのたびに再生成するコストが減るとのこと。
ここでつまづいたのが、このletが**遅延評価**するという特性による挙動です。

```user_factory.rb
FactoryBot.define do
  factory :user do
    first_name { '太郎' }
    last_name { '山田' }
    email {'testuser@example.com'}
  end
end
```

このようにFactoryBotでUserを作り、そのModelのテストを行うとします。

```user_spec.rb
describe '#full_name' do
  let(:user) { create(:user) }
  it '山田太郎を返す' do
    expect(user.full_name).to eq '山田太郎'
  end
end
```

ところがこれでは正常に動作しないです。
letが評価されるタイミングはuserを呼び出した時です。なのでfull_name実行時にはuserは作られておらずうまくいかないのです。

```user_spec.rb
describe '#full_name' do
  let!(:user) { create(:user) }
  it '山田太郎を返す' do
    expect(user.full_name).to eq '山田太郎'
  end
end
```

letの後ろに!をつけることで、itの中身が実行される前に中身が作られるようにするとうまくいきます。

# その2 サブドメインのテスト
こんな感じのルートがあった時に、サブドメイン配下のRequest specを書きたい場合はどうすればいいかハマりました。

```routes.rb
constraints subdomain: 'hoge' do
  get '/' => subdomain#index
end
```

正解はこちら⬇︎

```subdomain_request_spec.rb
describe 'GET index' do
  it "200を返すこと。" do
    headers = { "Host" => "hoge.example.com" }
    get '/', headers: headers
    expect(response).to have_http_status(:ok)
  end
end
```

このようにgetする際のheaderにHostを引っ付けてあげればいけるんですね〜
確認するとちゃんとサブドメインが取れています。

```
pry(#<RSpec::ExampleGroups::SubdomainController>) request.subdomain
  => "hoge"
```
# その3 モバイルのテストをしたい！
テンプレートをPCかスマホで出し分けている時、スマホでもテストをしたい場合はどうすればいいかです。
結論から言うとこれもheadersをいじります。

```request_spec.rb
describe 'GET index' do
  it "スマホで200を返すこと" do
    headers = { "Host" => "HTTP_USER_AGENT" => "Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X)" }
    get '/', headers: headers
    expect(response).to have_http_status(:ok)
  end
end
```

これでモバイルだけおかしい！みたいなこともなく安心。

# その4 セッションを持たせておきたい
ログイン先のページをテストする際などセッションがあることを前提とした部分をてすとしたいときです。
こうしましょう


```request_spec.rb
describe 'GET mypage' do
  setup do
    post "/login", params: {email: 'test@example.com', pass: 'testtest' }
  end

  it "200を返すこと" do
    get '/mypage'
    expect(response).to have_http_status(:ok)
  end
end
```

setupブロックで事前に/loginにpostしておくことでセッションを生成しておくことができます。
そのあとにexampleを書いていけばOK！

# その5 CarrierWaveを使っているところのテスト
Carrierwaveを使っていて画像をアップロードする部分のテストはどうすればいいの！

```upload_request_spec.rb

  before(:each) do
    allow_any_instance_of(ImageUploader).to receive(:store!).and_return(true)
  end

  let(:params) do
    {
      image: {
        file: Rack::Test::UploadedFile.new(File.join(Rails.root, 'spec/images/test.jpg')),
      }
    }
  end
  it 'Imageがアップロードされている。' do
    post '/upload', params: params
    expect(Image.count).to eq 1
  end
```

テスト用の画像ファイルをプロジェクト配下のどこかに置いておいて、読み込んであげればOKです。
私の場合は画像をS3にアップしていたのでその処理をスキップするためにUploaderのstore!メソッドをスタブしました。
本当は環境ごとに適切に分けておいた方がいいんでしょうが、、

# まとめ
Rspecを書いていく中で、個人的に印象に残っているつまづきポイントをざっと紹介しました。
こんな感じでつまづくところはつまづきつつ、日々テストコード書いてプロダクトの品質向上に努めております。


# お知らせ
エイチームグループでは一緒に活躍してくれる優秀な人材を募集中です。
興味のある方は[エイチームグループ採用ページ](https://www.a-tm.co.jp/recruit/requirements/career/lifestylesupport-webengineer/)よりご応募くださいませ。

Qiita Jobsの[エイチーム引越し侍社内システム企画 / 開発チーム](https://jobs.qiita.com/employers/a-hikkoshi/development_teams/176)、[社内システム開発エンジニアを募集！](https://jobs.qiita.com/postings/309)からチャットでご質問いただくことも可能でございます。

よろしくお願いいたします!

# 明日は
明日は弊社技術開発部の貴公子@nao_70さんの登場です。お楽しみに。