💎

【Rails】RSpec初心者による初心者のためのテストの書き方

2017/01/30に公開約5,100字

はじめに

こんにちは。RSpec5 日目の超初心者です。RSpec のコントローラーのテストについて、自分が初めて行ったコントローラーのテストのコードとともにまとめる記事にしたいと思います。

実装にあたり、たくさんの記事で勉強させていただきました。深く感謝いたします。
今回の記事はテストに関しての基本的な知識を前提としています。以下の記事が大変わかりやすいです。

使える RSpec 入門・その 1「RSpec の基本的な構文や便利な機能を理解する」

また、FactryGirl や Faker がインストール済みの環境で行います。
 Rails RSpec の基本 ~導入編~

今回テストを行ったのはメッセージを送信する処理を行う messages_controller です。
users と chat_groups が多対多関係、また、それぞれ messages と1対多関係を持ちます。

messages_controller.rb
class MessagesController < ApplicationController

  before_action :set_chat_group, only: %i(index create)

  def index
    @message = Message.new
  end

  def create
    @message = current_user.messages.new(message_params)
    if @message.save
      redirect_to chat_group_messages_path
    else
      flash[:alert] = "メッセージを入力してください。"
      render action: :index
    end
  end

  private
  def message_params
    params.require(:message).permit(:body, :image).merge(chat_group_id: params[:chat_group_id])
  end

  def set_chat_group
    @chat_group = ChatGroup.find(params[:chat_group_id])
  end
end

テストの書き方

コントローラーのテストの実装は

① テストケース設計
② テストデータ作成
③ テストロジック作成
④ リファクタリング

の順序で行います。テストの実装手順に関してはこちらの記事を参考にさせていただきました。このように段階的に実装していくことでテストの構造を理解しながら進めていくことができました。

#1.テストケース設計
 コントローラーのテストでは、1 つのアクションにつき 2 つの挙動をテストします。

① 正しいビューに変遷するか。
② インスタンス変数(アクション内で@〜で定義している変数)が期待された値を持つか。

つまり、今回のコントローラーのテストでは次のように設計します。

index アクション

messages_controller_spec.rb
describe 'GET #index' do
  it "正しいビューに変遷する" do
  end

  it "@messageが期待される値を持つ" do
  end

  it "@chat_groupが期待される値を持つ" do
  end
end

create アクション

messages_controller_spec.rb
describe 'Post #create' do
  context "@messageが保存できた時" do

    it "データベースに値が保存される" do
    end

    it "正しいビューに変遷する" do
    end

  end

  context "@messageが保存できなかった時" do

    it "データベースに値が保存されない" do
    end

    it "正しいビューに変遷する" do
    end

  end

 end

index アクションに関しては describe でのグループ化のみですが、create アクションは少し違っていますね。

messages_controller を見ると、create アクションでは条件分岐があります。条件分岐は、context という機能を用いて、全ての場合についてのテストを行います。context は describe と機能は変わりませんが、「条件分岐のテストである」ことを示すため(可読性を高めるため)、使用します。

2.テストデータ作成

テストデータの作成は以下の2つの手順で行います。

① ダミーデータの作成
② テストコントローラ内で定義

① ダミーデータの作成

factories/chat_groups.rb
FactoryGirl.define do

  factory :chat_group do
    name Faker::Team.name
  end
end

上記のようなファイルを用意することで、テストで利用できるダミーデータを作成します。
カラムの属性によって異なるダミーデータの作成方法はこちらが参考になります。

② テストコントローラ内で定義

index アクション

messages_controller_spec.rb
…
  it "@chat_groupが期待される値を持つ" do
    chat_group = create(:chat_group)
  end

chat_group = create(:chat_group)

このように定義することで作成したダミーデータを呼び出せます。今後、この作成したテストデータでテストを行っていきます。

以上のような流れで他の example のテストデータも作成します。

3.テストロジック作成

テストロジックの作成は以下の2つの手順で行います。

①HTTP メソッドの指定
② エクスペクテーションの実装

①HTTP メソッドの指定

index アクション

messages_controller_spec.rb
…
  it "@chat_groupが期待される値を持つ" do
    chat_group = create(:chat_group)
    get :index, chat_group_id: chat_group
  end

HTTPメソッド  :(コントローラーのアクション)
上記のように指定することで擬似的にコントローラーのアクションにリクエストできます。
さらに、パラメータが必要な場合は、ハッシュ形式で渡します。

image
messages の index アクションのパスを確認すると、chat_group_id をパラメータで持つことがわかります。
そのため、chat_group_id: chat_groupでパラメータを渡すことで、作成した chat_group のパスに擬似的にリクエストできるようになります。

② エクスペクテーションの実装

index アクション

messages_controller_spec.rb
…
  it "@chat_groupが期待される値を持つ" do
    chat_group = create(:chat_group)
    get :index, chat_group_id: chat_group
    expect(assigns(:chat_group)).to eq chat_group
  end

assigns メソッドを用います。assigns メソッドはコントローラーのインスタンス変数をテストするメソッドです。引数にインスタンス変数をシンボル型で渡します。

今回の場合、messages_controller.rb の index アクションで@chat_group というインスタンス変数を定義しているため、これのテストになります。

expect(assigns(:chat_group)).to eq chat_group

すなわち、この一行では、message_controller.rb の index アクションで定義されている@chat_group というインスタンス変数と、テスト内で作成した chat_group というテストデータが確かに同じであることを検証しています。これが同じであれば、インスタンス変数が期待された値を持つと言えることになります。

その他の example に関しても、テストしたい内容によってエクスペクテーションを実装していきます。

4.リファクタリング

let

messages_controller_spec.rb
describe MessagesController do
  let(:chat_group) { create(:chat_group) }

  describe 'GET #index' do
    it "renders the :index template" do
      ~~~
    end

    it "assigns the requested messsage to @message" do
      ~~~
    end

    it "assigns the requested chat_group to @chat_group" do
      get :index, chat_group_id: chat_group
      expect(assigns(:chat_group)).to eq chat_group
    end
  end
end

let でテストデータを一度定義してしまえば、それぞれ example でわざわざテストデータを定義する必要がなくなります。コード量が減らせ、可読性も高まるためテストデータを定義する際は let を用いたいですね。

そのほかにも様々なリファクタリングの方法があるようです。知っておけば量が少なく、可読性が高まるコードが書けるので紹介した記事などを参考にしてみてください。

最後に

以上のように段階的に分解して考えていくことで、少しずつテストを理解できました。間違っている点や改善点がありましたらご指摘お願いします。

参考

GitHubで編集を提案

Discussion

ログインするとコメントできます