【リクエストスペック①】APIの取得

はじめに

こんにちは!大ちゃんの駆け出し技術ブログです。

今回はタイトルにもある通り、リクエストスペックについて書きたいと思います。

しかし、リクエストスペックを説明するためにはAPIの説明が必要不可欠となるため、今回はAPI情報の取得方法、次回はAPI情報が取得できているかをテストするリクエストスペックという感じで2回構成で説明していきたいと思います!

他のテストタイプとの違い

「リクエストスペックってなんぞや?」

自分も最初はこんな感じでした。

自分がRSpecを学習する上で1番最初に学習したのはそのモデルをテストするモデルスペックです。主にテストの検証内容はそのモデルのバリデーションが効いているのかをテストすることです。

例: タスクモデルのモデルスペック

# spec/models/task_spec.rb
require 'rails_helper'

RSpec.describe Task, type: :model do
  describe 'validation' do
    it 'is valid with all attributes' do
      task = build(:task)
      expect(task).to be_valid
      expect(task.errors).to be_empty
    end

    it 'is invalid without title' do
      task_without_title = build(:task, title: nil)
      expect(task_without_title).to be_invalid
      expect(task_without_title.errors[:title]).to eq ["can't be blank"]
    end
  end
end

次に学習した内容はシステムスペックでした。主にこちらのテストは自分の期待通りに画面が遷移し対象の要素が画面に表示されているのか、実装した機能が正しく動くかを検証します。圧倒的にこちらのテストを書く機会が多いですね。

例: ログイン機能のシステムスペック

require 'rails_helper'

RSpec.describe 'UserSessions', type: :system do

  let(:user) { create(:user) }

  describe 'before login' do
    before { visit login_path }
    context 'when input values in the form are all valid' do
      before do
        fill_in 'Email', with: user.email
        fill_in 'Password', with: 'password'
        click_button 'Login'
      end
      it 'login successfully' do
        expect(page).to have_content 'Login successful'
        expect(current_path).to eq root_path
      end
    end
  end
end

これら2つのテストは何度も書いたことがありました。しかし、新たに今回リクエストスペックという言葉が出てきました。これは一体何のテストなんでしょうか。

ある記事を参照すると、リクエストスペックはどうやら以下のようなテストがリクエストスペックと言えるそうです。

基本、統合テストはシステムスペックを書きましょう API系のテストがあるなら、別でリクエストスペックを書きましょう

qiita.com

リクエストスペックとはシステムスペックと同じ統合テストにあたるようです。統合テストとは、モデルスペックのように個々に対して対してテストするのではなく、システムスペックのように集合的にテストする形式です。しかし、その以外はAPI系のテストの有無にあります。

APIってなんぞや?」

みたいになるかと思いますが、詳しく説明すると大変ボリューミーになるので、詳しく知りたい方は他記事をあたってみてください。簡単に引用して説明すると下記のようなものがAPIです。

APIを、簡単に説明すると「決まった方法でアクセスをすれば決まった結果を返してくれるもの」です。先のケーススタディでいうと、URLの末尾にzipcode=郵便番号でアクセスすると、住所を決まった形式で返してくれるのです。一般的にはJSONという形式で結果が返ります。郵便番号の例もJSON形式です。どこからでもアクセスできる口を開けておき、決まった形式でアクセスを受け付けて、仕様通りの結果を返すインターフェースとなるのがAPIです。

products.sint.co.jp

よくTwitterAPIだったり、ぐるなびAPIだったりとAPIという単語自体は聞いたことはあるという人も多いのではないでしょうか。APIとはまさにそれらアプリが公開している情報のことであることを示しています。そして、その情報にアクセスすると一般的にはJSONの形式で返すようですね。例えば、下記の情報をぐるなびAPIが公開しているお店の情報に見立てることもできます。いろいろな情報がこちらで調べることなくJSON形式で手に入ります。

https://i.gyazo.com/d1913f65c35122a2d33b463d0bceece2.png

API情報の公開とは

自分が使用しているアプリを例にどのようにAPI情報へアクセスするか試してみましょう。

sorceryのログイン機能に関してアプリ内で行っているのはログインユーザーのAPI情報の公開です。詳しくみてみると下記のような状態です。

# app/controllers/api/v1/authentication_controller.rb
module Api
  module V1
    class AuthenticationController < BaseController
      def create
        @user = login(params[:email], params[:password])
        raise ActiveRecord::RecordNotFound unless @user
        json_string = UserSerializer.new(@user).serialized_json
        render json: json_string
      end
    end
  end
end

何かいつものログイン機能のファイルとは違いますね。。。

いつものsorceryのログイン機能のファイルは以下のような感じかと思います。

def create 
  @user = login(params[:email], params[:password]) 
  if @user 
    redirect_back_or_to root_path 
  else 
    render :new 
  end 
end

共通部分は下記の部分のみです。

@user = login(params[:email], params[:password])

なぜ今回使うファイルがいつものsorceryのログイン機能のファイルとは違うのかというと、今回テストするファイルはログインユーザーのAPI情報を返す仕様になっているからです。ここはかなりややこしいですね。

言い直すのならば、いつもsorceryでログイン機能を実装しているcreateアクションは、アプリ内でログインするための機能です。しかし、今回テストするcreateアクションはログインしたユーザーのデータをJSON形式で外部に公開する機能です。いつものsorceryのcreateアクションとは違い、API情報を公開する機能を持っているということです。

具体的にJSON形式で公開する方法を説明します。

まず、共通部分では同じように登録されているユーザーを取得します。

@user = login(params[:email], params[:password])

もし、ユーザーがいないのであれば例外を発生させます。

raise ActiveRecord::RecordNotFound unless @user

次の箇所は少し歪に見えるかと思います。

json_string = UserSerializer.new(@user).serialized_json

これはfast_jsonapiのgemで生成したUserSerializerを用いて、取得したユーザー情報をJSON形式に変更しています。json_stringの中身を見てみると、user情報がJSON形式で格納されているのがわかります。

5: def create
6:   @user = login(params[:email], params[:password])
7:   raise ActiveRecord::RecordNotFound unless @user
8:   json_string = UserSerializer.new(@user).serialized_json
9: 
10:   binding.pry
11:   
12:   render json: json_string
13: end

[1] pry(#<Api::V1::AuthenticationController>)> json_string
=> "{\"data\":{\"id\":\"1\",\"type\":\"user\",\"attributes\":{\"name\":\"MyString1\",\"email\":\"MyText1\"}}}"

最後にJSONファイルをレンダリングしてます。

render json: json_string

このレンダリングを行うことで、ユーザー情報を外部からのアクセスで表示させることができるのです。

API情報へのアクセス方法

実際にアクセスして指定のユーザー情報を取得してみましょう。

このAPI情報をどのように取得するのかというと、上述したようにJSON形式のデータを取得するためには決まった形式のアクセスが必要です。今回のログインユーザーの情報にアクセスするためには、ルーティングを見るとわかります。

$ rails routes
api_v1_authentication POST   /api/v1/authentication(.:format)         api/v1/authentication#create {:format=>/json/}

Post形式で/api/v1/authenticationにアクセスする必要があるようです。

実際に指定のブラウザ上でも試せますが、Postmanというアプリを使うとAPI情報へのアクセスがわかりやすくなるので、Postmanを使います。

www.postman.com

Post形式で/api/v1/authenticationを指定した画面が下記になります。

https://i.gyazo.com/11cf3d9b106db0da9ff611bfb34d7b6e.png

しかし、このままではアクセスできません。今回はログイン機能ですので、アプリで登録されているユーザーの指定のパラメーター情報を/api/v1/authenticationに加える必要があります。

@user = login(params[:email], params[:password])

Postamanで送る方法はとても簡単で、Query Paramsという項目に登録されてるユーザー情報に合致するパラメータを記載します。取得パラメーターはemailpasswordです。

gyazo.com

するとURIの部分が自動で切り替わりました。

localhost:3000/api/v1/authentication?email=MyText1&password=password

これで指定のパラメーターを送ることが可能になったので、実際に送信ボタン(Send)を押してみましょう。するとJSON形式のデータが取得できたはずです。

{
    "data": {
        "id": "1",
        "type": "user",
        "attributes": {
            "name": "MyString1",
            "email": "MyText1"
        }
    }
}

ちなみにここで間違ったパラメーター渡してみると、ユーザー情報が取得できなかったために、raise ActiveRecord::RecordNotFoundの例外処理が実行され、下記のエラーメッセージのデータがJSON形式で表示されます。エラーハンドリングされているということですね。

{
    "message": "Record Not Found",
    "errors": [
        "ActiveRecord::RecordNotFound"
    ]
}

終わりに

上述したように今回はあくまでAPIの概要や取得方法についての説明であり、本当に説明したのは次回説明するリクエストスペックについてです。今回のようにJSON形式のファイルが表示されているのかをテストする方法について発信していきますのでお楽しみに!

以上、大ちゃんの駆け出し技術ブログでした!