Help us understand the problem. What is going on with this article?

Rails + RSpec + FactoryBot + FakerでTestを書いてみた。Part1

More than 1 year has passed since last update.

はじめに

Railsで個人開発しているのですが、Testというものをあまり書いたことがなかったので、書いてみようと思い書きはじめてみたものの、思いの外まとまっている記事がなかったの備忘録ついでにまとめてみました。
(*テスト初心者なので、間違い等あったらご指摘いただけると幸いです。)

環境

  • macOS High Sierra 10.13.6
  • Ruby 2.5.1
  • Ruby on Rails 5.1.4
  • gem
    • rspec-rails
    • factory_bot_rails
    • faker
    • devise  *ログイン周りをこれで実装しているためテスト実装前に設定が必要

RSpecとは

Ruby/Ruby on Railsで作ったクラスやメソッドが正常に動作するかをテストするためのドメイン特化言語 (DSL)のこと。

FactoryBotとは

テスト用のデータを作成してくれるツール。
もともとは、FactoryGirlという名前だったが、その名前に対して不快感を感じる人が多く、そのためFactoyBotという名前になったそう。

参考URL
【翻訳】"Factory Girl"が"Factory Bot"に変わった理由(と移行手順)

Fakerとは

様々なダミーデータを生成してくれるツール。

実際にやってみた

Relation

User has_many Groups
Group belongs_to User

事前準備

gemのインストール、RSpecのインストール

Gemfile
group :development, :test do
  gem 'rspec-rails', '~> 3.7'
  gem 'factory_bot_rails'
  gem 'faker'
end
$ bundle install

RSpecのインストール

$ bundle exec rails generate rspec:install

FactoryBotの設定

spec/rails_helper
RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

上記は、以下のようにrspecのテストコード中でFactoryBotのメソッドを使用する際、
上述のコードを加えることで、以下のようにrspecのテストコード中でFactoryBotのメソッドを使用する際に、名前空間を指定する必要がなくなります。

user = FactoryBot.create(:user)

user = create(:user)

超便利。

ちなみに、

今回、Deviseを採用しているので、以下の設定も必要。
Deviseを使っている方は、こちらを参考に設定してみてください!

参考URL
rspecのテスト環境でdeviseにログインする方法【rails】

テストデータの作成

users.rb
FactoryBot.define do
  factory :user do
    password = Faker::Internet.password(8)
    nickname { Faker::Name.last_name }
    email { Faker::Internet.free_email }
    password { password }
    password_confirmation { password }
  end
end
groups.rb
FactoryBot.define do
  factory :group do
    name { "名前" }
    host_name { "主催者の名前" }
    regulation { "参加条件" }
    explanation { "説明" }
    user
  end
end
groups_controller_spec.rb
require 'rails_helper'

RSpec.describe Public::GroupsController, type: :controller do
  let!(:user) { create(:user) }
  let!(:group) { create(:group) }
  let!(:group_attributes) { attributes_for(:group) }

  describe 'GET #index' do
    before do
      login_user user
    end

    it "responds successfully" do
      get :index
      expect(response).to be_success
    end

    it "gets index" do
      get :index
      expect(response.status).to eq(200)
    end
  end

  describe 'GET #show' do
    before do
      login_user user
    end

    it "assigns the requested group to @group" do
      get :show, params: { id: group.id }
      expect(response.status).to eq(200)
    end
  end

  describe 'GET #new' do
    before do
      login_user user
    end

   it 'assigns new @group' do
     expect(response.status).to eq(200)
   end
 end

  describe 'Post #create' do
    before do
      login_user user
    end

    context "when @group can be saved" do
      it "saves group on DB" do
        expect do
          post :create, params: { group: group_attributes }
        end.to change(Group, :count).by(1)
      end
    end

    context "when @group can not be saved" do
      it "doesn't save group on DB" do
        expect do
          post :create, params: {
            group: {
              name: "",
              host_name: "サンプル",
              regulation: "サンプル",
              explanation: "サンプル"
            }
          }
        end.to change(Group, :count).by(0)
      end
    end
  end

  describe 'GET #edit' do
    before do
      login_user user
    end

    it "assigns the requested group to @group" do
      get :edit, params: { id: group.id }
      expect(response.status).to eq(200)
    end
  end

  describe 'PATCH #update' do
    before do
      login_user user
    end

    let(:update_attributes) do
      { regulation: 'アップデート', explanation: 'アップデート' }
    end

    it 'saves updated group' do
      expect do
        patch :update, params: { id: group.id, group: update_attributes }, session: {}
      end.to change(Group, :count).by(0)
    end

    it 'updates updated group' do
      patch :update, params: { id: group.id, group: update_attributes }
      group.reload
    end
  end

  describe 'DELETE #destroy' do
    before do
      login_user user
    end

    it 'deletes group' do
      expect do
        delete :destroy, params: { id: group.id }, session: {}
      end.to change(Group, :count).by(0)
    end
  end
end

解説

今回は、以下の定義済みのアクションのテストを実装

  • index
  • show
  • new
  • create
  • edit
  • destroy
  • update

* resourcesで定義されるアクションすべてですね。

ソースコードを上から順に、

let!(:user) { create(:user) }
let!(:group) { create(:group, user: user) }
let!(:group_attributes) { attributes_for(:group) }

letを使うことで、それぞれ変数を定義しています。
定義することで、それぞれexample内でテストデータを定義する必要がなくなり、コード量が減らせ、可読性も高まります。letで定義された変数の状態は、他のテストに引き継がれないのがミソ!!
上記はそれぞれテスト内で、

user
group
group_attributes

として使うことができます。

index

まずは、indexアクションから。
describeは、テストの説明が入ります。
以下のように使います。

describe '説明' do
・・・
end

今後も出てくるので、必須です!

before do
  login_user user
end

こちらは、describe内のテストが実行される前に、実行する処理になります。
(login_userは、rspecのテスト環境でdeviseにログインする方法【rails】 で設定した、controller_macros.rbに定義されています。)

it "responds successfully" do
  get :index
  expect(response).to be_success
end

it "get index" do
  get :index
  expect(response.status).to eq(200)
end

上は、indexに対し、getでアクセスしたときに、期待したresponseがbe_successであったかどうか、ということです。
下は、indexに対し、getでアクセスしたときに、正常なレスポンス(200番)が返ってきているかをテストしています。

show

describe 'GET #show' do
  before do
    login_user user
  end

  it "responds successfully" do
    get :show, params: { id: group.id }
    expect(response).to be_success
  end

   it "assigns the requested group to @group" do
     get :show, params: { id: group.id }
     expect(response.status).to eq(200)
   end
end

上は、showに対しgetでアクセスしたときに、期待したresponseがbe_successであったかどうか、ということです。
下は、showに対しgetでアクセスしたときに、正常なレスポンス(200番)が返ってきているかをテストしています。

*indexとの違いは、showアクションでは、id(ここでは、groupのid)が必要なので、paramsで設定する必要があります。

長くなってきたので、一旦ここまで。

次回は、newからスタート

つづく

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away