はじめに
本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルのRequestSpec編の記事になります。(今回はVue.jsには触れませんのであしからず)
前回: Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(ファイル投稿編)
次回: Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(2要素認証設定編)
Rspecの導入
今回はmini-testではなくRspecを用いてテストを行います。理由は筆者がRspecの方が好きだからです(mini-testはあまり触ったことがない、、、)
Gemfileの編集
Gemfileに以下を追加して、bundle installを実行してください。
# Gemfile
group :development, :test do
gem "rspec-rails"
gem "factory_bot_rails"
gem 'faker' # ダミーデータを作成するため
gem 'gimei' # 日本語のダミーデータを作成するため
end
# Terminal
$ bundle
Generatorの実行
その後、Rspecを導入する以下のコマンドを実行してください。
$ rails g rspec:install
create .rspec
create spec
create spec/spec_helper.rb
create spec/rails_helper.rb
.rspecファイルの中身を以下のように修正してください。
--require spec_helper
--format documentation
--color
--format documentationはRspecの実行結果を見やすく加工するため、--colorは実行結果を色つきにして見やすくするために追記しています。
application.rbの編集
rails g model
やrails g scaffold
を実行した時に余計なファイルが作成されないように、generator実行時の挙動を修正します。
config/application.rbを以下のように修正してください。
# config/application.rb
module App
class Application < Rails::Application
# 省略
# ここから追加
config.generators do |g|
g.test_framework :rspec,
view_specs: false,
helper_specs: false,
controller_specs: false,
routing_specs: false
end
# ここまで
end
end
rails_helper.rbの修正
FactoryBotの省略記法を使うために、以下の行を追加してください。
RSpec.configure do |config|
# 省略
# 以下を追加
config.include FactoryBot::Syntax::Methods
# 省略
end
これで、Factorybot.create(:user)
ではなく、create(:user)
のように書くことができます。
ログインのヘルパーモジュールを作成
RequestSpecの中でログイン状態を表現したり、getやpostメソッド等のHTTPリクエストメソッドをオーバーライドするヘルパーモジュールを定義します。
以下の実装方法を参考にしました。
以下のコマンドを実行してください。
$ mkdir spec/supports/
$ touch spec/supports/auth_helper.rb
作成したファイルを以下のように修正します。
module AuthHelper
HTTP_HELPERS_TO_OVERRIDE = [:get, :post, :patch, :put, :delete]
HTTP_HELPERS_TO_OVERRIDE.each do |helper|
define_method(helper) do |path, **args|
add_auth_headers(args)
args == {} ? super(path) : super(path, **args)
end
end
def login(user)
@user = user
@auth_token = @user.create_new_auth_token
end
def logout
@user = nil
@auth_token = nil
end
private
def add_auth_headers(args)
return unless defined? @auth_token
args[:headers] ||= {}
args[:headers].merge!(@auth_token)
end
end
修正できたらrails_helper.rbでこのファイルをincludeします。
RSpec.configure do |config|
# 省略
# ここから追加
Dir[Rails.root.join('spec/supports/**/*.rb')].each { |f| require f }
config.include AuthHelper, type: :request
# ここまで
# 省略
end
これでRequestSpecの中で、オーバーライドされたgetメソッドやpostメソッド、login、logoutメソッドを実行することができます。
RequestSpecの作成
投稿APIのRequestSpecを作成します。
以下のコマンドを実行してください。
$ mkdir spec/requests
$ mkdir spec/factories
$ touch spec/requests/posts_spec.rb
$ touch spec/factories/posts.rb
$ touch spec/factories/users.rb
まずはFactoryファイルから編集しましょう。
# spec/factories/users.rb
FactoryBot.define do
factory :user do
provider { 'email' }
uid { Faker::Internet.safe_email }
password { Faker::Internet.password }
email { uid }
name { Gimei.name }
end
end
# spec/factories/posts.rb
FactoryBot.define do
factory :post do
title { Faker::Lorem.word }
body { Faker::Lorem.sentence }
association :user
end
end
これでspecの中でcreate(:user)
等が実行できます。
次にposts_spec.rbを書いていきます。
require 'rails_helper'
RSpec.describe "Posts API", type: :request do
describe "ログイン済" do
describe "GET /posts" do
it "投稿の一覧を取得できること" do
user = create(:user)
post = create(:post, user: user)
login user
get "/posts"
expect(response).to have_http_status(:success)
post_json = JSON.parse(response.body).first
expect(post_json["id"]).to eq post.id
expect(post_json["title"]).to eq post.title
expect(post_json["body"]).to eq post.body
expect(post_json["created_at"]).to eq post.created_at.iso8601(3)
expect(post_json["user_name"]).to eq post.user.name
end
end
describe "GET /posts/:id" do
it "投稿の詳細を取得できること" do
user = create(:user)
post = create(:post, user: user)
login user
get "/posts/#{post.id}"
expect(response).to have_http_status(:success)
post_json = JSON.parse(response.body)
expect(post_json["id"]).to eq post.id
expect(post_json["title"]).to eq post.title
expect(post_json["body"]).to eq post.body
expect(post_json["created_at"]).to eq post.created_at.iso8601(3)
expect(post_json["user_name"]).to eq post.user.name
end
end
describe "POST /posts" do
it "投稿を作成できること" do
user = create(:user)
login user
params = attributes_for :post
post "/posts", params: params
expect(response).to have_http_status(:success)
post_json = JSON.parse(response.body)
post = Post.find(post_json["id"])
expect(post_json["id"]).to eq post.id
expect(post_json["title"]).to eq post.title
expect(post_json["body"]).to eq post.body
expect(post_json["created_at"]).to eq post.created_at.iso8601(3)
expect(post_json["user_name"]).to eq post.user.name
end
end
describe "PATCH /posts/:id" do
it "投稿を更新できること" do
user = create(:user)
post = create(:post, user: user)
login user
params = attributes_for :post
patch "/posts/#{post.id}", params: params
expect(response).to have_http_status(:success)
post_json = JSON.parse(response.body)
_post = Post.find(post_json["id"])
expect(post_json["id"]).to eq _post.id
expect(post_json["title"]).to eq _post.title
expect(post_json["body"]).to eq _post.body
expect(post_json["created_at"]).to eq _post.created_at.iso8601(3)
expect(post_json["user_name"]).to eq _post.user.name
end
end
end
describe "未ログイン" do
it "投稿一覧にアクセスができないこと" do
user = create(:user)
post = create(:post, user: user)
get "/posts"
expect(response.status).to eq 401
end
end
end
authenticate_user!をbefore_actionでコールしているため、ログイン済と未ログイン済のテストケースを用意しています。
ログイン済の場合は、各種APIを叩いたあとに返却される返り値が正しいかどうか、
未ログインの場合は401エラーが返却されるかどうかをテストしています。
まとめ
最低限APIのテストを行うなら上記の実装で十分かと思います。
次回はユーザー登録機能を実装する予定です。