rails5 rspecでテスト書きながらapi開発、circleciも使ってciぽいことしてみる
はじめに
今回やりたいことはざっくり以下
・rails5でrest api実装
・devise_token_authを使って認証機能を実装
・rspecでテストコード実装
・circleciで動かす
それぞれ少ししか触れないがなんとなくciぽい雰囲気がわかるように手順メモしておく
事前準備
なにはともあれrailsプロジェクト作成
今回のテストはrspecで書くので--skip-test-unitオプションを指定してtestディレクトリを作らないようにする
$ rails new spec_test --api --skip-test-unit
db作成。ローカルで動かすだけなのでとりあえずsqliteで
$ bundle exec rake db:create
ここまでできたらとりあえず動かしてみる。以下のコマンドでサーバー起動して「http://localhost:3000」にアクセスしてページが表示されればok
$ rails s
rspec
bundleインストールしてセットアップ。
factory_botとdatabase_cleanerはたぶん今後最低限使いそうなので一緒にいれておく
factory_botはテストデータの作成に、database_cleanerは登録したテストデータがゴミとして残らないように削除するために使う
$ vi Gemfile gem 'rspec-rails' gem 'factory_bot_rails' gem 'database_cleaner' ・・・ $ bundle install --path=vendor/bundle $ bundle exec rails g rspec:install
以下を記述しておけば「rails g controller」とかしたときにrailsが自動でrspecのひな形を作ってくれる。
今回はrequest specしか作らないので以下のようにした。
$ vi config/application.rb
・・・
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
view_specs: false,
helper_specs: false,
routing_specs: false,
controller_specs: false,
request_specs: true
g.fixture_replacement :factory_bot, dir: "spec/factories"
endadtabase_cleanerでrspec開始前にテーブルをクリア(truncate)。
各exampleごとにトランザクションを貼ってロールバックする形となる。
今回はexampleごとにテストデータを準備することにしたのでこの形。
参考)https://qiita.com/shoichiimamura/items/25942acc1d1bd78ef9c3
$ vi spec/spec_helper.rb
## 追加
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
RSpec.configure do |config|
・・・
## 追加
config.before(:suite) do
DatabaseCleaner.strategy = :truncation
DatabaseCleaner.clean_with(:truncation)
end
config.before(:each) do
DatabaseCleaner.start
end
config.after(:each) do
DatabaseCleaner.clean
end
end以下はfactory_botを使うときのおきまりの記述ぽい、たぶん
テストデータを作るときに「FactoryBot::create」と書かないといけないところを「create」と書けるようになる
$ spec/rails_helper.rb RSpec.configure do |config| ・・・ config.include FactoryBot::Syntax::Methods end
とりあえずrspecの設定は完了したので1度実行してみる。テストはないがエラーが出ないことを確認しておく
$ ./bin/bundle exec rspec --format documentation No examples found. Finished in 0.1503 seconds (files took 1.34 seconds to load) 0 examples, 0 failures
devise_token_auth
今回は以下を実装、テストすることとする
・メールアドレス+パスワードをパラメータでサインアップ
・メールアドレス+パスワードをパラメータでログイン
・ログイン済みでないとアクセスできないエンドポイントの実装
ログイン済みかどうかの判定方法はログインに成功したときにレスポンスヘッダーに認証情報が含まれるので以降のリクエストではその値をリクエストヘッダーに含める形となる
bundleインストール
$ vi Gemfile gem 'jbuilder', '~> 2.5' gem 'rack-cors' gem 'devise' gem 'devise_token_auth' ・・・ $ bundle install --path=vendor/bundle
以下のコマンドでuserモデルが認証の対象となり、userモデルが作られる
$ rails g devise_token_auth:install User auth
また、今回はsignup後にメールアドレスが存在するかのチェックまでは行わないので以下のようにconfirmableの部分をコメントアウトして無効とする
$ vi db/migrate/xxx_devise_token_auth_create_users.rb
・・・
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
・・・
# add_index :users, :confirmation_token, unique: true
・・・
$ ./bin/rake db:migrate
実装
devise_token_authを使うと以下のapiが自前で実装しなくても作られるので今回はそれのテストコードを書く。
signin : POST /auth/sign_in
signup : POST /auth
もちろんsignin、signup以外の機能も作られるがいっぱいあるのでここでは扱わない
まずはFactoryBotで生成するテストデータはspec/factories以下に定義する
$ vi spec/factories/users.rb
FactoryBot.define do
factory :user do
sequence(:name) { |n| "test_#{n}" }
sequence(:email) { |n| "test+#{n}@example.com" }
password { 'password' }
end
endコードの中「create(:user)」 と記述すればこれを使って以下のようなテストデータを生成してくれる
nの部分は連番でcreateしたrspect内でcreateした回分インクリメントされていく
{ name: "test_0", email: "test+0@example.com", password: "password" }
signupのテスト(spec)
request specはrequests以下に実装していく。
コードみればなんとなくわかりそうだが、リクエストしてレスポンスが期待値どおりか検証するコードを書いていく
「let!(:user) {create(:user)}」で定義したタイミングでFatoryBotでuserのテストデータが1件作られる
なので、実際に/auth/sign_inにリクエストするときにDBに必ず存在するユーザーでログインのテストする形となる
requests/auth_spec.rb
require 'rails_helper'
RSpec.describe 'Auth', type: :request do
let!(:user) {create(:user)}
describe 'POST /auth/sign_in' do
context '正常' do
before do
post '/auth/sign_in', params: {email:user['email'], password: 'password'}
end
example 'HTTPステータスが200であること' do
expect(response.status).to eq(200)
end
example 'レスポンスが正しいこと' do
json = JSON.parse(response.body)
expect(json['data']['email']).to eq(user['email'])
end
end
context '異常' do
before do
post '/auth/sign_in', params: {email:user['email'], password: 'passwordxxx'}
end
example 'HTTPステータスが401であること' do
expect(response.status).to eq(401)
end
example 'レスポンスが正しいこと' do
json = JSON.parse(response.body)
expect(json['success']).to eq(false)
end
end
end
endテストを実行する
$ bundle exec rspec --format documentation spec/requests/auth_spec.rb
Auth
POST /auth/sign_in
正常
HTTPステータスが200であること
レスポンスが正しいこと
異常
HTTPステータスが401であること
レスポンスが正しいこと上記のようになり期待通りの動作であることが確認できた
signinのテスト(spec)
Signupと同じようにテストを書く
RSpec.describe 'Auth', type: :request do
・・・
describe 'POST /auth' do
context '正常' do
let(:params) {{email:'test@example.com', password:'password'}}
before do
post '/auth', params: params
end
example 'HTTPステータスが200であること' do
expect(response.status).to eq(200)
end
example 'レスポンスが正しいこと' do
expect(JSON.parse(response.body)['status']).to eq("success")
end
end
end
endテストを実行する
$ bundle exec rspec --format documentation spec/requests/auth_spec.rb
Auth
・・・
POST /auth
正常
HTTPステータスが200であること
レスポンスが正しいこと
認証が必要なapi
devise_token_authでの認証処理は
ログインに成功するとレスポンスヘッダーにuid、client、access-token返す
認証が必要なapiへリクエストする際に取得したヘッダーをリクエストヘッダーに含めてリクエストする
認証に成功すればcurrent_userに認証に成功したuserモデルがセットされるのでログイン中かどうかの判定ができる
/usersにリクエストして認証済みなら本人の情報をかえすapiとする
controllers/users_controller.rb
class UsersController < ApplicationController
def index
if current_user.nil?
render status: 401, json: {status: 401} if current_user.nil?
else
render json: current_user
end
end
endconfig/routes.rb
Rails.application.routes.draw do
mount_devise_token_auth_for 'User', at: 'auth'
get '/users', to: 'users#index'
end続いてテストコード
/usersにリクエストする前にログインのapiへリクエスト後に取得したヘッダーを付与してリクエストするようにする
spec/requests/users_spec.rb
require 'rails_helper'
RSpec.describe 'Users', type: :request do
let!(:user) {create(:user)}
def auth_headers
post '/auth/sign_in', params: {email:user['email'], password: 'password'}
{ 'uid'=>response.header['uid'], 'client'=>response.header['client'], 'access-token'=>response.header['access-token'] }
end
describe 'GET /users' do
context '未ログインの場合' do
example 'HTTPステータスが401であること' do
get '/users'
expect(response.status).to eq(401)
end
end
context 'ログインずみの場合' do
before do
get '/users', headers: auth_headers
end
example 'HTTPステータスが200であること' do
expect(response.status).to eq(200)
end
example 'レスポンスが正しいこと' do
expect(JSON.parse(response.body)['id']).to eq(user['id'])
end
end
end
endテスト実行
$ bundle exec rspec --format documentation spec/requests/auth_spec.rb ・・・
結果書かないけどSuccessになるはず
circleci
あとはcircleciの設定すればgithubにプッシュするタイミングでテストを行ってくれる
circleciの挙動確認するために毎回プッシュしなくてもローカルにcircleciをインストールすれば動作確認可能なのでcircleciをインストールする
circleci localインストール
$ curl -o /usr/local/bin/circleci https://circle-downloads.s3.amazonaws.com/releases/build_agent_wrapper/circleci && chmod +x /usr/local/bin/circleci circleci update
localでcircleci
.circleci/config.yml
version: 2
jobs:
build:
docker:
- image: circleci/ruby:2.4.1-node-browsers
working_directory: ~/repo
steps:
- checkout
- restore_cache:
key: bundle-{{ checksum "Gemfile.lock" }}
- run:
name: bundle install
command: bundle install --path=vendor/bundle
- save_cache:
key: bundle-{{ checksum "Gemfile.lock" }}
paths:
- vendor/bundle
- run:
echo -e "export RAILS_ENV=test" >> $BASH_ENV
- run:
name: test
command: bundle exec rspec --format documentationローカルでcircleci実行
$ circleci build .circleci/config.yml
circleciで実行
githubとcircleciの画面からポチポチ連携するだけなので省略
ダメだ。。力尽きた。。以上です
参考)
[Rails] rspec + factory_bot + database_cleaner で、APIのテストを書く - Qiita
Rails5へのRspec導入から実行確認まで - Qiita