Rspecの使い方 - くま's Tech系Blog

くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Rspecの使い方

今回はRspecについてまとめようと思います。

RSpecは、Rubyで書かれたアプリケーションの挙動・機能をテストするために利用されます。(テストフレームワーク)

導入

まずはRspecを使えるようにします。

group :test do
  gem "rspec-rails"
end

Gemfileにrspec-railsというGemを追加して、bundle installを行います。

Gemをインストールしたら、rails generate rspec:installコマンドでセットアップを行います。

成功すれば、specフォルダが作成されているはずなので、確認しましょう。

rails generate rspec:installを実行する前に、config/application.rbに下記設定を追加することで、specフォルダ内にデフォルトで作成されるファイルを作成されないように設定できます。

config.generators do |g|
      g.test_framework :rspec,
                       fixtures: false,   # テストデータを作るfixtureを作成しない
                       view_specs: false,   # テストデータを作るfixtureを作成しない
                       helper_specs: false,   # ヘルパー用のスペックを作成しない
                       routing_specs: false   # ルーティングのスペックを作成しない
    end

これで、RSpecを実行する準備が整いました。

テストの配置ルールと命名規則

テストを実行するためにはファイルの配置ルールと命名規則に従う必要があります。

Ruby on Railsの場合は、次のように配置します。

 spec/
   models/
      モデルファイル名_spec.rb
   controllers/  
      コントローラファイル名_spec.rb
   views/
      erbファイル名_spec.rb

このように、Ruby on Railsディレクトリ構成と同じ階層構成でspecディレクトリ配下に配置します。

Rspecの基本構造

Rspecに限らず、基本的なテストはデータを準備→確認したい処理を呼び出す→結果が想定通りか確認という流れになると思います。

その流れの中で、Rspecの書き方を見ていきます。

まずは、ファイルの先頭にはrequire 'rails_helper'をつけてください!

describe

まず、describeでテストの対象が何かを記述します。

describe "計算を確認するテスト" do
end

このように日本語でも書くことができます(もちろん、英語の可能です)

また、どのクラスのテストケースかを指定するだけでなく、 type: :modeltype: :controllerを指定することで、どのモジュールのテストかを指定できます。

controllerのテストで type: :requestを指定できます。type: :requestはURLの接続テストになるので、APIのテストという意味合いをもちます。(エンドポイントやPOST/GETなどのメソッドの種類を指定して実行します)

RSpec.describe Bookmark, type: :model do
end

context

contextは特定の条件や実施する際の条件を記載します。

describe "計算を確認するテスト" do
  context "マイナスの2つの数字の足し算" do
    calculator = Calculator.new
    result = calculator.add(-1, -2)
  end
end

contextはネストすることができるので、1つのdescribeの中に複数のcontextを記載できます。

describe "計算を確認するテスト" do
  context "2つの数字を加算" do
    context "2つのプラスの数字を加算" do
    end

    context "2つのマイナスの数字を加算" do
    end
  end

  context "2つの数字を減算" do
  end
end

it

itはアウトプットの内容を記載します。

describe "計算を確認するテスト" do
  context "マイナスの2つの数字の足し算" do
    calculator = Calculator.new
    result = calculator.add(-1, -2)

    it "-3が出力される(マイナス通しの足し算で想定通りになる)" do
       expect(result).to eq(-3)
    end
  end
end

expectメソッドの引数にテスト対象コード(オブジェクト)を渡します。 expect(...).to につづけてマッチャーを書きます。 今回はeqでresultと-3が一致するかを確認しています。
resultと-3が一致しない場合にはテストケースの実行で失敗します。

eq以外にtrueかfalseかをチェックするbe_validなどマッチャーはいろんな種類があるので、公式ドキュメントを確認するといいでしょう。(最後の参照にリンク貼ってます)

before

beforeは各テストケースの前に実行されるコードブロックです。

テストケースの前にデータの準備を行いたいパターンやいろんなテストケースで共通に行いたい処理を記載するなどができます。

describe "計算を確認するテスト" do
  before do
    # セットアップコード
  end

  it "マイナス通しの足し算" do
    # テストケースの実装
  end

  it "0が含まれた足し算" do
    # テストケースの実装
  end

  it "マイナスの数字とプラスの数字の足し算" do
    # テストケースの実装
  end
end

let

letは変数を定義する際に使用します。 describeかcontextの中でのみ使用可能です。

letとlet!の2種類あります。 letとlet!の違いは実行タイミングです。 letは利用時に実行され、let!は書かれた場所で実行されます。

letは遅延評価とも言われています。 { create(:post,user: user) }はテストが実行されるまで実際には評価されません。

let(:post) { create(:post,user: user) }

let!にするとテストが始まる前に{ create(:post,user: user) }が実行され、 その結果がlet!の後の:postにセットされます。

let!(:post) { create(:post,user: user) }

つまり、letはあるテストケース内で変数を複数回使用するが、その値が変更されない時に便利です。 そして、let!はあるテストケース内で変数の値が変更される可能性があり、テストケースごとに初期化が必要な時に便利です。

テストの実行

specフォルダに移動して、bundle exec rspecコマンドで全テストケースを実行します。

bundle exec rspec ./spec/ファイル名 というコマンドで特定のファイルに絞ってテスト実行もできます。

データ作成

テストデータを作成するときにはRspec標準で使えるfixtureに代わり、テストデータの準備をサポートしてくれるFactorybotを使用します。 fixtureを使用することもありますが、今回はFactorybotについて記載します。

Gem追加

Gemfileにfactory_bot_railsというGemを追加して、bundle installを行います。

group :test do
  gem "rspec-rails"
  gem "factory_bot_rails"
end

ファイル作成

rails g factory_bot:model モデル名

上記コマンドでfactoryファイルを作成します。test/factoriesに作成されます。

モデル作成

ファイル作成で作られたファイルがUserだと仮定して、Userのテストデータを作成します。

FactoryBot.define do
  factory :user do
    sequence :email do |n|
      "person#{n}@example.com"
    end
    password { "Password1" }
    password_confirmation { "Password1" }
    name { "test" }
    status { 0 }
end

password { "Password1" }はpasswordが固定で"Password1"という値になっていることを表しています。

sequence :email do |n|
  "person#{n}@example.com"
end

上記はnの部分が連番でデータが作成されます。

データを呼び出す

作成したテストデータを呼び出す場合には下記のようにcreate(:user)もしくは、build(:user)とすることで作成したデータを使用できます。

require "rails_helper"

describe "xxxxx" do
  before do
    @user = create(:user) #ここで定義
  end

  context "xxxxx" do
    xxxxx
  end
end

createとbuildには下記違いがあります。

create

  • DB上にインスタンスを永続化する。
  • DB上にデータを作成する。
  • DBにアクセスする処理のときは必須。(何かの処理の後、DBの値が変更されたのを確認する際は必要)

build

  • メモリ上にインスタンスを確保する。
  • DB上にはデータがないので、DBにアクセスする必要があるテストのときは使えない。
  • DBにアクセスする必要がないテストの時には、インスタンスを確保する時にDBにアクセスする必要がないので処理が比較的軽くなる。

さらに、データを上書きすることもできます。

@user = create(:user, name: "test更新")

このようにすることで、nameがtest更新のユーザーデータを作成できます。

trait

traitはパターンA、B 、C などデータをパターン分けして準備できる機能です。

FactoryBot.define do
  factory :user do
    sequence :email do |n|
      "person#{n}@example.com"
    end
    password { "Password1" }
    password_confirmation { "Password1" }
    name { "test" }
    status { 0 }

    trait :fixed_id do
        id { 100 }
    end
end

上記は先ほどUserモデルにidが固定バージョンのデータを追加しました。

let(:user) { create(:user, :fixed_id) }のようにfixed_idを追加することで指定したデータにできます。

こうすることで、毎回idが固定は困るけど、特定のケースではidを固定で使用したいなど使い分けができます。

複数データを作成する

複数データを作成する場合に、個数分createするのは骨が折れる作業です。

そこで、FactoryBotのcreate_listメソッドを使って複数個のデータを一気に作成できます。

users = create_list(:user, 5)

または、Userの生成時に、Taskも一緒に生成したい場合には下記のようにすることができます。

trait :with_tasks do
      after(:create) do |user|
        create_list(:task, 5, user:)
      end
    end

TransientとEvaluatorを使用した関連データの生成

trasientはファクトリの生成時に動的なデータにする属性です。

transientをcallback関数内で参照したい場合、evaluatorを使います。

callback関数のブロック引数でevaluatorを宣言することで、transientの値を参照することができます。

FactoryBot.define do
  factory :user do
  end

  trait :with_tasks do
      transient do
        tasks_count { 5 }
      end
      after(:create) do |user, evaluator|
        # evaluatorを経由して、transientのtasks_countを参照している
        create_list(:knowledge, evaluator.tasks_count, user:)
      end
    end
end

上記はUserのwith_tasksで、生成するtaskの数を指定したい場合の定義です。

画像のサンプルを用意

画像のサンプルを用意する場合にはspec/fixtures配下に画像を置いてください。

画像のアップロードを行うテストはfixture_file_uploadメソッドを使用します。

例えば、下記はアップロードされた画像とみなすことができます。

let(:image1) { fixture_file_upload("spec/fixtures/image.png", "image/png") }

おまけ

type: :requestでheaderを指定するのは簡単ですが、type: :controllerの場合には少し工夫が必要です。

describe 'Sample' do
    let(:header) { { 'X-Requested-With': "XMLHttpRequest" } }
    let(:params) { { 'TEST': "SAMPLE" } }
 
    it 'set header' do
      post 'path/to/endpoint', params: params, headers: header
      expect(response.status).to eq 200
    end
end

type: :controllerの場合にはheaderを指定するだけで適用されますが、type: :controllerでは指定できません。

require 'rails_helper'

RSpec.describe Api::LoginsController, type: :controller do
  
   let(:header) { { 'X-Requested-With': "XMLHttpRequest" } }

  describe 'logout' do
    it 'returns status 204' do
      request.headers.merge!(header)
      post :logout

      expect(response.status).to eq(204)
    end
  end
end
end

type: :controllerで使用する場合にはrequest.headersに追加したいヘッダー情報をマージしましょう。

参照

rspec.toolboxforweb.xyz

hackmd.io

zenn.dev

github.com

qiita.com

qiita.com

qiita.com