概要

Ruby on rails で開発中のAPIサーバのテストを、rspecを用いて行うまでにやったことの備忘録。
本記事では、APIのテストにrspecを、テスト用のデータの生成にfactory_botを、テスト前後でデータベースを初期化する処理をdatabase_cleanerで行う。

前提

以下環境で動作確認

ubuntu 16.04.2
ruby 2.4.1
rails 5.1.2
rspec 3.7.1
factory_bot 4.8.2
database_cleaner 1.6.2

以下の状態まで出来上がっていることを想定

  • Railsによる何らかのAPIが存在する
    • 特定のモデルがある -- GET /api/[モデル名] で、モデルの一覧を取得できる
  • bundleでgemを管理している

本記事の目的

本記事では、前提の条件から発展させて、以下のことをできるようにする

  • rspecを用いて、APIのアクション単位でのテストを書けるようにする
  • テストの対象となるデータは、FactoryBotを用いてテストコード内で動的に作成する
  • テストの対象となるデータは、テスト内だけで使用し、テスト終了後に全て削除するようにする
  • テストの対象となるデータは、開発用とデータベースをわけ、開発用データに影響を与えないようにする

必要なgemのインストール

Gemfileを以下のように編集する。既に何らかのgemが記述済みの場合、消す必要はない。

group :development, :test do
  gem 'rspec-rails'
  gem 'factory_bot_rails'
  gem 'database_cleaner'
end
$ bundle install

rspecの初期設定

ジェネレータを使ってrspecの初期ファイルを自動生成する

$ bundle exec rails generate rspec:install
      create  .rspec
      create  spec
      create  spec/spec_helper.rb
      create  spec/rails_helper.rb

生成された spec/rails_helper.rbに、以下の一行を追加することで、テストコード中でのFactoryBotの名前空間指定を省略できるようにする

RSpec.configure do |config|
  # 中略
  config.include FactoryBot::Syntax::Methods
end

さらに、 spec/spec_helper.rb に、以下を追加することで、テストごとに生成されたテストデータを自動で削除する仕組みを導入する。

RSpec.configure do |config|
  # 中略
  config.before(:suite) do
    DatabaseCleaner.strategy = :truncation
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
  # 以下略
end

Factoryの作成

テストデータを作成してくれるモジュール(Factory)を作成する。本記事ではFactoryBotを利用する。基本的なFactoryBotの使い方は以下がオススメ

本記事では、名前と誕生日を持ったUserモデルが存在しているという体で、Userモデルを生成してくれるFactoryを作成する。

spec/factoriesディレクトリを作成し、その中にusers.rbを作成する。

$ mkdir factories
$ cd factories
$ vim users.rb

users.rbでは、factory_botを用いて、Userモデルを生成するためのFactoryを定義する。生成されるユーザは、名前が"TEST_NAME_1","TEST_NAME_2"...と、生成毎に連番になるようし、誕生日は固定で2017-01-01になるように定義する。

FactoryBot.define do

  factory :user do
    sequence(:name) { |n| "TEST_NAME_#{n}" }
    birthday '2017-01-01'
  end

end

軽く動作確認をすると以下のようになる。

irb(main):013:0* FactoryBot.build(:user).name
=> "TEST_NAME_1"
irb(main):014:0> FactoryBot.build(:user).name
=> "TEST_NAME_2"
irb(main):015:0> FactoryBot.build(:user).name
=> "TEST_NAME_3"
irb(main):016:0> FactoryBot.build(:user).name
=> "TEST_NAME_4"

テスト用データベースの作成

Railsでは、開発用データベースとテスト用データベースと、本番用データベースを分けて使用することができる。プロジェクト名がappであるとき、app_testが、テスト用データベースになる。

以下のコマンドを用いて、テスト用のデータベースを作成してマイグレーションを実行する。シーダが存在する場合は別途シーダも回す。

$ bundle exec rails db:migrate RAILS_ENV=test

テストコードの作成

APIのテストコードは、specディレクトリ内のrequests以下に作成し、さらに今回はモデル単位でディレクトリを分けるので、usersディレクトリもまとめて作成する。usersディレクトリ内で、index_spec.rbを作成し、このファイルにて、usersの一覧取得APIのテストを記述する。

$ mkdir requests/users -p
$ cd requests/users
$ vim index_spec.rb

index_spec.rbでは、テスト実行時に先程作成したFactoryを用いて、ユーザを100人作成してから、APIにて100ユーザを取得できることを検証する。

RSpec.describe 'Users', type: :request do

  FactoryBot.create_list(:user, 100)

  describe "GET /api/users" do
    it '全件取得できる' do
      get api_users_path
      expect(JSON.parse(response.body).count).to eq 100
    end
  end

end

テストの実行

テストは、Railsのルートディレクトリに戻って、以下のコマンドで実行できる。(-f d は出力フォーマットの指定。無くてもテストは実行できる)

$ bundle exec rspec -f d

Users
  GET /api/users
    全件取得できる

Finished in 0.40263 seconds (files took 1.92 seconds to load)
1 example, 0 failures

テストでは、ユーザが100人居ることを検証したが、開発用DB及びテスト用DBには影響が残っていないことが確認できる。

mysql> use app_development;
Database changed
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|       27 |
+----------+
1 row in set (0.00 sec)

mysql> use app_test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select count(*) from users;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

もし実行時に以下のようなエラーが出た場合

$ bundle exec rspec -f d

An error occurred while loading ./spec/requests/users/index_spec.rb.
Failure/Error: FactoryBot.create_list(:user, 100)

NameError:
  uninitialized constant FactoryBot
# ./spec/requests/users/index_spec.rb:3:in `block in <top (required)>'
# ./spec/requests/users/index_spec.rb:1:in `<top (required)>'
No examples found.
[/bash]
[bash]
$ bundle exec rspec -f d
bundler: failed to load command: rspec (/root/appname/vendor/bundle/ruby/2.4.0/bin/rspec)
NameError: uninitialized constant Rails

spec_helper.rbに、以下のような記述を追加すると解決するかも

ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'

RSpec.configure do |config|
  # 中略
  config.include Rails.application.routes.url_helpers
end

参考

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.