はじめに
自分は2021年に新卒でWeb系の開発会社にフロントエンジニアとして入社し2022年で2年目になります。
実務ではReact×TypeScriptを利用したフロント周りの開発やRailsを用いたAPIの開発を行なっています。
今回は実務でRailsプロジェクトにテストを導入することになり「RSpec」について改めて学び直したのでその内容を紹介します。
はじめにRSpecの概要を説明し、その上でRails APIのCRUDの具体的なテストコードを書いていきます。
この記事の対象者
- Railsのテストを学びたい人
- Rails APIでのテストの書き方を知りたい人
- RSpecに入門したい人
この記事でやらないこと
- 環境構築
- RSpecに関しての詳しい解説
なお本記事は、下記の記事の続きとなっています。
そのためDocker上でRailsのAPIモードの環境構築をしていない人は準備した上こちらの記事に進んでいただければと思います。
この記事の目標
- RailsAPIでCRUDのテストを書けるようになる
RSpecとは
そもそもRSpecとは公式ドキュメントより
RSpecはRubyプログラマのためのBehaviour-Driven Developmentツールです。BDDとは
テスト駆動開発、ドメイン駆動設計、受入テスト駆動設計を組み合わせたソフトウェア開発のアプローチです。
そして受入テスト駆動計画を組み合わせたソフトウェア開発のアプローチです。RSpecはこのうち、TDDの部分を支援します。
と説明されています。
RSpecを使うことでRailsでテストを実行することができるということだけ今回は把握しておいてください。
より詳しい解説についてはRSpecの公式ドキュメントを読んでみてください。
テストを書く理由
テストを書く主な理由は、
- 開発効率と開発体験を上げる
- 開発製品の品質の担保
- 画面に不具合が発生し、ユーザーの期待通りの挙動にならないことを防ぐ
また具体的にはテストを導入することで、
- 開発中に早い段階でエラーを発見しやすくし開発コストを減らすことができる
- 機能が増えた時に、既存の機能を壊さない可能性を上げる
- ユーザーの操作が期待通りかをチェックできる
といった恩恵を得ることができます。
テストを書く理由についてより詳しい解説はこちらの記事がわかりやすかったのでぜひ参考にしてみてください。
テストを書く準備及びテストコードの記述
まずはじめにRailsAPIモードでのテスト書いて実行するまでの流れを解説していきます。
テストコードの記述から実行までの流れは下記の通りです
- RSpecの導入
- テストファイルの作成
- テスト用のデータベースの作成
- テストコードの実装
- テストの実行
RSpecの導入
まずRailsテストを行うためにRSpec
をインストールしていきます。
Gemfile
に下記の記述を書き、docker compose build
を実行します。
gem 'rspec-rails'
gem 'factory_bot_rails'
ビルドを実行
docker compose build
これでRSpecを使う準備は完了しました。
テストファイルの作成
次にテスト用のファイルを作成します。
下記のコマンドを実行しposts
をテストするためのファイルを作成します。
docker-compose run web rails g rspec:install
docker-compose run web rails g rspec:request posts_api
テスト用のデータベースを作成
次にテストで利用するためのデータベースを作成していきます。
docker-compose.yml
に下記の記述を追加します。
test-db:
image: postgres
environment:
POSTGRES_PASSWORD: password_test
POSTGRES_DATABASE: postgres_test
ports:
- "5431:5432"
ビルドとデータベースのリセット及びマイグレーションを実行
docker-compose build
docker-compose run web rails db:reset
docker-compose run web rails db:migrate
テストコードの実装
ここからは具体的にCRUDのテストコードを書いていきます。
まずテスト用のメソットを使えるようにするためspec/spec_helper.rb
に下記の一行を追加します。
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.include FactoryBot::Syntax::Methods # これを追加する
次にテストデータのサンプルを作成するためにspec/factories/posts.rb
を作成し下記のコードを書きます。
FactoryBot.define do
factory :post do
title { "サンプル記事" }
end
end
factory_bot
を使うことでRSpec
で用いるためのテストデータの作成を簡単に行うことができます。
factory_bot
についてり詳しい解説はこちらを参考にしてみてください。
GETリクエストのテスト
まずはじめに投稿(Post)を全件取得するテストコードを記述します。
全件取得するコントローラー内のロジックは下記の通りです。
class PostsController < ApplicationController
before_action :set_post, only: [:show, :update, :destroy]
# GET /posts
def index
@posts = Post.all
render json: @posts
end
end
こちらのリクエストが正しい挙動かをテストしていきます。
コードの解説はコメントにて付与しています。
require 'rails_helper'
describe 'GET /posts' do
it '記事の全件取得' do
# spec/factories/posts.rbで定義したテストデータを10件複製(配列)
FactoryBot.create_list(:post, 10)
# /postsのエンドポイントへGETリクエスト
get '/posts'
# 返り値( render json: @posts)を変数に格納
json = JSON.parse(response.body)
# リクエスト成功を表す200が返ってきたか確認する。
expect(response.status).to eq(200)
# 10件のデータが返ってきているかを確認
expect(json.length).to eq(10)
end
end
テストを実行
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
コントローラーの内容を変更してテストを失敗させてみます。
# GET /posts
def index
# 1件だけ返すように変更
@posts = Post.first
render json: @posts
end
テストを実行
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
GETの詳細リクエスト
次に特定の記事を取得するテストを書いてきます。
対象のコントローラーのロジックは下記です。送られてきたidの記事だけを返す処理になっています。
class PostsController < ApplicationController
before_action :set_post, only: [:show, :update, :destroy]
# GET /posts/1
def show
render json: @post
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def post_params
params.permit(:title)
end
end
テストコードは下記です。
describe 'GET /posts/:id' do
it '特定の記事を取得する' do
# テストデータを1件作成
post = create(:post, title: "サンプル記事")
# /posts/#{post.id}へGETリクエスト
get "/posts/#{post.id}"
# 返り値を変数へ格納
json = JSON.parse(response.body)
# リクエスト成功を表す200が返ってきたか確認する。
expect(response.status).to eq(200)
# テストデータで作成した値が返ってきているかを確認
expect(json["title"]).to eq(post["title"])
end
end
テストを実行
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
先程作成した全件取得のテストと合わせて2件のテストが成功していることが確認できます。
POSTリクエスト
次に新規に記事を登録するPOSTリクエストをテストしていきます。
before_action :set_post, only: [:show, :update, :destroy]
# POST /posts
def create
@post = Post.new(post_params)
if @post.save
render json: @post, status: :created, location: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
# Only allow a trusted parameter "white list" through.
def post_params
params.permit(:title)
end
テストコードを書きます。
describe 'Post /posts' do
# リクエストで送られてくるテストデータ
before do
@post_create_params = {
title: "新規投稿"
}
end
it '新しいタスクを作成する' do
# 受け取ったテストデータをパラメタとし新規作成
# Postデータが作成されているかをテスト(件数が1つ増えているか)
expect { post '/posts', params: @post_create_params }.to change(Post, :count).by(+1)
expect(response.status).to eq(201)
end
end
テストコード実行します。
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
PUTリクエスト
次に更新の処理が正しく動くかをテストしていきます。
before_action :set_post, only: [:show, :update, :destroy]
# PATCH/PUT /posts/1
def update
if @post.update(post_params)
render json: @post
else
render json: @post.errors, status: :unprocessable_entity
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def post_params
params.permit(:title)
end
テストコードを書きます。
describe "PUT /posts/:id" do
it '記事の更新' do
# 更新対象のテストデータを作成
post = create(:post)
# 更新用のリクエストデータ
@post_update_params = {
title: "更新しました"
}
# PUTリクエスト
put "/posts/#{post.id}", params: @post_update_params
expect(response.status).to eq(200)
# 更新後のデータとリクエストデータが一致しているかを確認
expect(post.reload.title).to eq(@post_update_params[:title])
end
end
テストコード実行します。
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
DELETEリクエスト
最後に削除のリクエスト処理が正しく動いているかをテストしていきます。
before_action :set_post, only: [:show, :update, :destroy]
# DELETE /posts/1
def destroy
@post.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_post
@post = Post.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def post_params
params.permit(:title)
end
テストコードを書きます。
describe 'Delete /posts/:id' do
it '記事をを削除する' do
# テストデータを1件削除
post = create(:post)
# DLETEにリクエストを送る
# 作成したテストデータが削除されている事を確認
expect { delete "/posts/#{post.id}" }.to change(Post, :count).by(-1)
# リクエスト成功を表す204が返ってきたか確認する。
expect(response.status).to eq(204)
end
end
テストを実行します。
docker-compose run web bundle exec rspec spec/requests/posts_apis_spec.rb
最後に
いかがだったでしょうか。
今回はRails APIでのテストの書き方について解説をしていきました。
ぜひRailsのテスト周りについて学習をしたい人は参考にしていただけると嬉しいです。
他にも記事を出しているので合わせて読んでいただけると幸いです。