【入門】Rails APIのテスト手法まとめ

## はじめに

自分は2021年に新卒でWeb系の開発会社にフロントエンジニアとして入社し2022年で2年目になります。

実務ではReact×TypeScriptを利用したフロント周りの開発やRailsを用いたAPIの開発を行なっています。

今回は実務でRailsプロジェクトにテストを導入することになり「RSpec」について改めて学び直したのでその内容を紹介します。

はじめにRSpecの概要を説明し、その上でRails APIのCRUDの具体的なテストコードを書いていきます。

## この記事の対象者

- Railsのテストを学びたい人
- Rails APIでのテストの書き方を知りたい人
- RSpecに入門したい人

## この記事でやらないこと

- 環境構築
- RSpecに関しての詳しい解説

なお本記事は、下記の記事の続きとなっています。

https://qiita.com/KNR109/items/d7adfd7cc068c2456670

そのためDocker上でRailsのAPIモードの環境構築をしていない人は準備した上こちらの記事に進んでいただければと思います。

## この記事の目標

- RailsAPIでCRUDのテストを書けるようになる

## RSpecとは

そもそもRSpecとは[公式ドキュメント](https://relishapp.com/rspec/)より

> RSpecはRubyプログラマのためのBehaviour-Driven Developmentツールです。BDDとは
テスト駆動開発、ドメイン駆動設計、受入テスト駆動設計を組み合わせたソフトウェア開発のアプローチです。
そして受入テスト駆動計画を組み合わせたソフトウェア開発のアプローチです。RSpecはこのうち、TDDの部分を支援します。

と説明されています。

RSpecを使うことでRailsでテストを実行することができるということだけ今回は把握しておいてください。

より詳しい解説についてはRSpecの公式ドキュメントを読んでみてください。

https://rspec.info/

## テストを書く理由

テストを書く主な理由は、

- 開発効率と開発体験を上げる
- 開発製品の品質の担保
- 画面に不具合が発生し、ユーザーの期待通りの挙動にならないことを防ぐ

また具体的にはテストを導入することで、

- 開発中に早い段階でエラーを発見しやすくし開発コストを減らすことができる
- 機能が増えた時に、既存の機能を壊さない可能性を上げる
- ユーザーの操作が期待通りかをチェックできる

といった恩恵を得ることができます。

テストを書く理由についてより詳しい解説はこちらの記事がわかりやすかったのでぜひ参考にしてみてください。

https://qiita.com/jnchito/items/2a5d3e15761fd413657a

## テストを書く準備及びテストコードの記述

まずはじめにRailsAPIモードでのテスト書いて実行するまでの流れを解説していきます。

テストコードの記述から実行までの流れは下記の通りです

- RSpecの導入
- テストファイルの作成
- テスト用のデータベースの作成
- テストコードの実装
- テストの実行

### RSpecの導入

まずRailsテストを行うために```RSpec```をインストールしていきます。

```Gemfile```に下記の記述を書き、``docker compose build``を実行します。

```Dockerfile:Dockerfile
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``に下記の記述を追加します。

```yml: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``に下記の一行を追加します。

```ruby:spec/rails_helper.rb
RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.include FactoryBot::Syntax::Methods # これを追加する
```

次にテストデータのサンプルを作成するために``spec/factories/posts.rb``を作成し下記のコードを書きます。

```ruby:spec/factories/posts.rb
FactoryBot.define do
  factory :post do
    title { "サンプル記事" }
  end
end
```

``factory_bot``を使うことで``RSpec``で用いるためのテストデータの作成を簡単に行うことができます。

``factory_bot``についてり詳しい解説はこちらを参考にしてみてください。

https://github.com/thoughtbot/factory_bot

#### GETリクエストのテスト

まずはじめに投稿(Post)を全件取得するテストコードを記述します。

全件取得するコントローラー内のロジックは下記の通りです。

```ruby:app/controllers/posts_controller.rb
class PostsController < ApplicationController
  before_action :set_post, only: [:show, :update, :destroy]

  # GET /posts
  def index
    @posts = Post.all
    render json: @posts
  end
end
```

こちらのリクエストが正しい挙動かをテストしていきます。
コードの解説はコメントにて付与しています。

```ruby:spec/requests/posts_apis_spec.rb
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
```
このようになっていれば成功です。
![スクリーンショット 2022-08-31 8.55.58.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/20e33931-ac6a-26cd-65e6-102b1f262b40.jpeg)

コントローラーの内容を変更してテストを失敗させてみます。

```ruby:app/controllers/posts_controller.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
```
テストが失敗していることが確認できます。
![スクリーンショット 2022-08-31 9.03.51.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/23bd63ec-eb7d-ec60-9cf5-f843e9860ac8.jpeg)

#### GETの詳細リクエスト

次に特定の記事を取得するテストを書いてきます。

対象のコントローラーのロジックは下記です。送られてきたidの記事だけを返す処理になっています。

```ruby:app/controllers/posts_controller.rb
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
```

テストコードは下記です。

```ruby:spec/requests/posts_apis_spec.rb
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件のテストが成功していることが確認できます。

![スクリーンショット 2022-08-31 9.11.25.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/2263c6bc-e77c-0011-5648-7df58ebade12.jpeg)

#### POSTリクエスト

次に新規に記事を登録するPOSTリクエストをテストしていきます。

```ruby:app/controllers/posts_controller.rb
  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
```

テストコードを書きます。

```ruby:spec/requests/posts_apis_spec.rb
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
```

![スクリーンショット 2022-08-31 9.31.20.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/7d4eb7b8-68fc-fa46-7e05-1eb545cf31e8.jpeg)

#### PUTリクエスト

次に更新の処理が正しく動くかをテストしていきます。

```ruby:app/controllers/posts_controller.rb
  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
```

テストコードを書きます。

```ruby:spec/requests/posts_apis_spec.rb
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
```

![スクリーンショット 2022-08-31 9.44.55.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/c82aba9a-b5b6-5349-bf72-a55ca7f73b01.jpeg)

#### DELETEリクエスト

最後に削除のリクエスト処理が正しく動いているかをテストしていきます。

```ruby:app/controllers/posts_controller.rb
  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
```

テストコードを書きます。

```ruby:spec/requests/posts_apis_spec.rb
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
```

![スクリーンショット 2022-08-31 9.48.39.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/67d3076f-1d99-928e-5113-931d1a17a4ff.jpeg)

## 最後に

いかがだったでしょうか。

今回はRails APIでのテストの書き方について解説をしていきました。

ぜひRailsのテスト周りについて学習をしたい人は参考にしていただけると嬉しいです。

他にも記事を出しているので合わせて読んでいただけると幸いです。

https://qiita.com/KNR109/items/7cf6b24bed318dab5715

https://qiita.com/KNR109/items/5d4a1954f3e8fd8eaae7

https://qiita.com/KNR109/items/e50c981e0518141be28f