LoginSignup
3
2

More than 1 year has passed since last update.

Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(RequestSpec編)

Last updated at Posted at 2021-04-29

はじめに

本記事は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 modelrails 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のテストを行うなら上記の実装で十分かと思います。

次回はユーザー登録機能を実装する予定です。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2