🧘

RSpecを導入しテストカバレッジも出す!(Rails APIモード)

2024/02/23に公開

はじめに

今まで業務でRailsを触ってきましたが、業務都合上テスト導入まで行うことができませんでした。しかしRailsを触っていく中で、RSpecという文字を何度も目にしたため、個人開発の環境で触ってみることとしました。

導入

必要なGemを追加する

  • 以下Gemを追加します
    • rspec-rails
    • factory_bot_rails
      gemfile
group :development, :test do
  ~
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  ~
end
  • Gemfileに追加したらbundle install
$ bundle install

テストに必要なファイルを生成

$ bundle exec rails g rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb****
  • 試しにテストを実行する
    • テストコードがないので0 examples, 0 failuresとなります
$ bundle exec rspec

Finished in 0.00021 seconds (files took 0.06641 seconds to load)
0 examples, 0 failures

bin/rspec コマンドを作成する

  • 下記によりbin/rspecにてテスト実行できる
$ bundle binstubs rspec-core
  • 実行できるか確認
$ bin/rspec spec/

Finished in 0.00021 seconds (files took 0.06641 seconds to load)
0 examples, 0 failures
  • 以下のようにディレクトリ、ファイル単位でテスト実行可能
# spec/ディレクトリ配下全て実行
$ bin/rspec spec/

# spec/hoge.rbのみ実行
$ bin/rspec spec/hoge.rb

不要なファイルは作成しないようにする

  • 今回、APIモードのためviewファイル用のテストファイルは必要ないため、生成しないようにします。

config/application.rb

require_relative "boot"

require "rails/all"

Bundler.require(*Rails.groups)

module Api
  class Application < Rails::Application
    config.load_defaults 7.0

    config.api_only = true
    # 追加
    config.generators do |g|
      g.assets false
      g.helper false
      g.test_framework :rspec,
        fixtures: false,
        view_specs: false,
        helper_specs: false,
        routing_specs: false
    end
  end
end

FactoryBotの設定

  • FactoryBotのメソッドを利用するために下記を設定
    spec/rails_helper.rb
RSpec.configure do |config|
  ~
  # 追加
  config.include FactoryBot::Syntax::Methods
end

テストコードを書く

  • 今回書くテストコードは前回書いた記事で作成したコントローラに対して書いていきます。詳しくは下記リンクからご覧ください

https://zenn.dev/hrk_sgyumm23/articles/eb8b9085bbff2e

  • 今回はrequestsディレクトリを作成しその中にテストコードを書いていきます

FactoryBotによるテスト対象のモデルのファクトリ定義

spec/factories/location_post.rb

FactoryBot.define do
    factory :location_post do
        title { "サンプル投稿" }
    end
end  

index

spec/requests/location_posts_controller_spec.rb

require 'rails_helper'

describe 'GET /posts' do
  it '全件取得' do
    # spec/factories/location_posts.rbで定義したテストデータを5件作成
    FactoryBot.create_list(:location_post, 5)
    # エンドポイントへGETリクエスト
    get 'http://localhost:3001/api/v1/location_posts'
    json = JSON.parse(response.body)

    # リクエスト成功を表す200が返ってきたか確認
    expect(response.status).to eq(200)
    # 10件のデータが返ってきているかを確認
    expect(json.length).to eq(5)
  end
end
  • テスト実行
$ bin/rspec spec/requests

GET /location_posts
  全件取得

Finished in 0.10582 seconds (files took 1.05 seconds to load)
1 example, 0 failures

show

spec/requests/location_posts_controller_spec.rb

describe 'GET /location_posts/:id' do
    it '特定の投稿を取得する' do
        # テストデータを1件作成
        location_post = create(:location_post, title: "hokeCafe", description: "良い", address:"五反田")
        # /posts/#{post.id}へGETリクエスト
        get "http://localhost:3001/api/v1/location_posts/#{location_post.id}"
        # 返り値を変数へ格納
        json = JSON.parse(response.body)
        # 200が返ってきたか確認
        expect(response.status).to eq(200)
        # テストデータで作成した値が返ってきているかを確認
        expect(json["title"]).to eq(location_post["title"])
        expect(json["description"]).to eq(location_post["description"])
        expect(json["address"]).to eq(location_post["address"])
    end
end
  • テスト実行
$ bin/rspec spec/requests

GET /location_posts
  全件取得

GET /location_posts/:id
  特定の投稿を取得する

Finished in 0.1152 seconds (files took 1.05 seconds to load)
2 examples, 0 failures

Coverage report generated for RSpec to /api/coverage. 64 / 75 LOC (85.33%) covered.

create

spec/requests/location_posts_controller_spec.rb

describe 'Post /location_posts' do
    # リクエストで送られてくるテストデータ
    before do
        @location_post_create_params = {
            location_post: {
                title: "hokeCafe",
                description: "良い",
                address:"五反田"
            }
        }
    end
    it '新しい投稿を作成する' do
        # 受け取ったテストデータをパラメタとし新規作成
        # Postデータが作成されているかをテスト(件数が1つ増えているか)
        expect { 
            post 'http://localhost:3001/api/v1/location_posts', 
            params: @location_post_create_params
        }
        .to change(LocationPost, :count).by(+1)
        expect(response.status).to eq(201)
    end
end
  • テスト実行
$ bin/rspec spec/requests

bin/rspec spec/requests

GET /location_posts
  全件取得

GET /location_posts/:id
  特定の投稿を取得する

Post /location_posts
  新しい投稿を作成する

Finished in 0.13722 seconds (files took 1.05 seconds to load)
3 examples, 0 failures

update

spec/requests/location_posts_controller_spec.rb

describe "PUT /location_posts/:id" do
    it '投稿の更新' do
        # 更新対象のテストデータを作成
        location_post = create(:location_post, title: "hokeCafe", description: "良い", address:"五反田")
        # 更新用のリクエストデータ
        @location_post_update_params = {
            location_post: {
                title: "hakeCafe",
                description: "良い",
                address:"五反田"
            }
        }
        # PUTリクエスト
        put "http://localhost:3001/api/v1/location_posts/#{location_post.id}", params: @location_post_update_params
    
        expect(response.status).to eq(200)
        # 更新後のデータとリクエストデータが一致しているかを確認
        expect(location_post.reload.title).to eq(@location_post_update_params[:location_post][:title])
        expect(location_post.reload.title).to eq(@location_post_update_params[:location_post][:description])
        expect(location_post.reload.title).to eq(@location_post_update_params[:location_post][:address])
    end
end
  • テスト実行
$ bin/rspec spec/requests

GET /location_posts
  全件取得

GET /location_posts/:id
  特定の投稿を取得する

Post /location_posts
  新しい投稿を作成する

PUT /location_posts/:id
  投稿の更新

Finished in 0.15425 seconds (files took 1.08 seconds to load)
4 examples, 0 failures

destroy

spec/requests/location_posts_controller_spec.rb

describe 'Delete /posts/:id' do
    it '記事をを削除する' do
        # テストデータを1件削除
        location_post = create(:location_post, title: "hokeCafe", description: "良い", address:"五反田")
        # 作成したテストデータが削除されている事を確認
        expect { delete "http://localhost:3001/api/v1/location_posts/#{location_post.id}" }.to change(LocationPost, :count).by(-1)
        # リクエスト成功を表す204が返ってきたか確認する。
        expect(response.status).to eq(204)
    end
end
  • テスト実行
$ bin/rspec spec/requests

GET /location_posts
  全件取得

GET /location_posts/:id
  特定の投稿を取得する

Post /location_posts
  新しい投稿を作成する

PUT /location_posts/:id
  投稿の更新

Delete /posts/:id
  記事を削除する

Finished in 0.16811 seconds (files took 1.07 seconds to load)
5 examples, 0 failures

テストカバレッジを出す

次にテストがどのくらい成功しているかを確認するため、テストカバレッジを出す設定をしていきます。調べているとSimpleCovなるGemを使ってテストカバレッジを出す事ができると分かったため導入します。

SimpleCov導入

https://github.com/simplecov-ruby/simplecov

Gemfile

~
group :development, :test do
https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  # 追加
  gem 'simplecov', require: false, group: :test
end
~
  • bundle install
$ bundle install
  • rails_helper.rbの先頭行にSimpleCovの設定を追加します

rails_helper.rb

# 追加
require 'simplecov'
SimpleCov.start

# This file is copied to spec/ when you run 'rails generate rspec:install'
~

前項で作成したテストを再度実行

$ bin/rspec spec/requests

bin/rspec spec/requests

GET /location_posts
  全件取得

GET /location_posts/:id
  特定の投稿を取得する

Post /location_posts
  新しい投稿を作成する

PUT /location_posts/:id
  投稿の更新

Delete /posts/:id
  記事をを削除する

Finished in 0.16811 seconds (files took 1.07 seconds to load)
5 examples, 0 failures

Coverage report generated for RSpec to /api/coverage. 72 / 75 LOC (96.0%) covered.
  • 最後の行に以下のような主力が追加されているかなと思います
Coverage report generated for RSpec to /api/coverage. 72 / 75 LOC (96.0%) covered.
  • またcoverage配下にindex.htmlが追加されておりそちらからGUI形式で確認することも可能です

終わりに

  • 読んでいただきありがとうございました。Railsを書いていたにもかかわらずRSPecに触れることができていなかったためいい勉強の機会となりました。
  • 今後としてはGithubActionsに組み込んでリモートpushされた時点でテスト実行&&カバレッジ表示とかできるとより実践的かなと思いました!(できたら記事にしたい、、)

Discussion