【リクエストスペック②】リクエストスペックの作成
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
1日空けてしまいましたが、本日はリクエストスペックの記述方法についてまとめていきたいと思います。
先日出した記事ではAPIの概要などを簡単に紹介しました。
本記事ではいよいよリクエストスペックを書いていきます!
テスト内容は前回の記事の中で紹介した『ログインユーザーの情報にアクセスできること』です。正しいパラメータを入力した上で、POST形式でlocalhost:3000/api/v1/authentication
にアクセスすると下記JSON形式の情報が取得できことを検証します。
{ "data": { "id": "1", "type": "user", "attributes": { "name": "MyString1", "email": "MyText1" } } }
ファイルの作成
早速スペックファイルを作成します。
他のシステムスペックやモデルスペックでもファイル作成用のコマンドがありますが、リクエストスペックにもあります。
$ bin/rails g rspec:request ファイル名
今回はapi/v1/
配下のファイルをテストするため、テストファイルも同様にapi/v1/
配下のフォルダに格納した方が良さそうです。どうするかというとファイル名の手前にApi::V1::
を置くだけです。そして今回テストするファイルはauthentication
ですので、ファイル名もそれに合わせて指定しましょう。
$ bin/rails g rspec:request Api::V1::authentication
こちらを実行すると指定したディレクトリにフォルダが作成されました。
$ bin/rails g rspec:request Api::V1::authentication
Running via Spring preloader in process 6744
create spec/requests/api/v1/authentications_spec.rb
無事ファイルが作成されました。
ファイルの中身を説明
作成されたフォルダの中身を見てみましょう。
# spec/requests/api/v1/authentications_spec.rb require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "GET /api/v1/authentications" do it "works! (now write some real specs)" do get api_v1_authentications_path expect(response).to have_http_status(200) end end end
いつものシステムスペックやモデルスペック作成時とはかなり見た目が違うのではないでしょうか。
まず、type: :request
の部分からわかる通り、テストの種類がリクエストであることがわかると思います。
その下のdescribe
の中身は現状「GET形式で/api/v1/authentications
にアクセスする」という記述になっていますね。これはリクエストスペックの作成時はデフォルトでGET形式が設定されているからです。今回テストするのはPOST形式ですので後ほどこちらは修正します。
その下のitの中身も目新しいかなと思います。
get api_v1_authentications_path
expect(response).to have_http_status(200)
get api_v1_authentications_path
については直感的に理解可能かと思います。describe部分で記述している通り、「GET形式で/api/v1/authentications
にアクセス」を行っています。こちらはHTTPリクエストの形式です。これを例えばPOST形式にする場合、post api_v1_authentications_path
を使うことができます。
次の検証部分ですが、これはHTTPステータスコードの検証をしています。HTTPステータスコードを知らないという方はMDNの説明をご覧ください。
HTTP レスポンスステータスコードは、特定の HTTP リクエストが正常に完了したどうかを示します。レスポンスは 5 つのクラスに分類されています。 1. 情報レスポンス (
100
–199
), 2. 成功レスポンス (200
–299
), 3. リダイレクト (300
–399
), 4. クライアントエラー (400
–499
), 5. サーバエラー (500
–599
)
have_http_status(200)
とすることでステータスコードが200、つまりHTTPリクエストが正常に完了したことを検証しています。反対に200を400などにすることでクライアントエラーが発生するかどうかを検証することができます。
リクエストスペックについて少し理解ができたのではないでしょうか?
POST形式でのアクセス方法
それではPOST形式でlocalhost:3000/api/v1/authentication
に正しいパラメータでアクセスすると、JSON形式の情報が取得できことを検証するテストを書いていきます。テストのマッチャは記載せず、describe
、context
、it
のみを記載して検証ファイルの流れを作ります。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentication" do context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do end end end end
それでは正しいパラメータを入力する方法から紹介していきます。
UserのFactoryBotは現在以下のように定義されています。
FactoryBot.define do factory :user do sequence(:name) { |n| "MyString#{n}" } sequence(:email) { |n| "MyText#{n}" } password { 'password' } end end
このFactoryBotのユーザーでログインしたとき、ログインユーザーの情報を取得できるかどうかを検証します。リクエストスペックファイル内ではlet!
を使用してユーザーを新規作成します。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do end end end end
次にPOST形式でlocalhost:3000/api/v1/authentication
にアクセス方法に移ります。これは先に記述を見せてから説明します。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } let(:password) { 'password' } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do post api_v1_authentication_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' } end end end end
post api_v1_authentication_path
ならわかると思います。POST形式でlocalhost:3000/api/v1/authentication
にアクセスしています。問題は後半部分のheaders
以降になるかと思います。
headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }
これを簡単に説明している記事を見つけましたので共有します。
ACCEPTとは「クライアント側がどんなデータを処理できるか」を表しています。つまり受け取るファイル形式であるJSONを許容(ACCEPT)しているということです。受け取るファイルはapplication/json
を指定しています。そもそもこの記述がないとテストでJSON形式のファイルを取得できないという意味にも取れます。
CONTENT_TYPEとは「実際にどんな形式のデータを送信したか」を表しています。サーバー側からクライアント側へ渡すファイル形式がJSONであるということです。こちらもapplication/json
を指定しているため、このJSONファイルを送信することを指定しています。
つまり、ACCEPTとCONTENT_TYPEに同じファイル(application/json
)指定することで、JSONファイルの送受信を可能にしているということです。JSONデータを取得するリクエストスペックでは必須の記述になると思います。
次に、正しいパラメータを入力する方法です。下記画像のQuery Paramsの入力部分になります。
これはparamsオプションを使って指定することができます。
RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } let(:password) { 'password' } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do post api_v1_authentication_path, headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }, params: { email: user.email, password: user.password }.to_json end end end end
パラメータ取得部分
params: { email: user.email, password: password }.to_json
こちらもJSON形式でのリクエストという理由でto_json
を指定しています。また、passwordのパラメータはuser.passwordでは入力できません。user.passwordは'password'をハッシュ化したものだからです。
crypted_password: "$2a$10$HO18dOf1JpzNaQ2Cq7JwHOdNe/KMEurfmt7e6sYZ.AxkfnFvF7jBa"
したがって、let(:password) { 'password' }
で変数password
をパラメータ部分に指定することでハッシュ化前の正しいパスワードを入力しています。
これでPOST形式でlocalhost:3000/api/v1/authentication
にアクセスできるようになりましたが、postメソッド部分がオプションのせいで少し頭でっかちですので、変数request_hash
にまとめてしまいましょう。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } let(:password) { 'password' } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do request_hash = { headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }, params: { email: user.email, password: password }.to_json } post api_v1_authentication_path, request_hash end end end end
リクエストスペックのマッチャ
それでは実際にマッチャを使って検証してみましょう。
まず、POST形式でのアクセスは成功しているはずですので、HTTPステータスコードは200を返しているハズです。よって上述したhave_http_status(200)
を使用してみましょう。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } let(:password) { 'password' } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do request_hash = { headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }, params: { email: user.email, password: password }.to_json } post api_v1_authentication_path, request_hash expect(response).to have_http_status(200) end end end end
テストはパスしたハズです。しかし、これだけだとアクセスは成功していることはわかりますが、期待するJSONファイルを取得できているのかがわかりません。
どのようにしてJSONファイルを取得するのかというと、JSON.parse(response.body)
で取得することができます。テストをbinding.pryで止めて確認してみましょう。
JSON.parse(response.body) => {"data"=>{"id"=>"1", "type"=>"user", "attributes"=>{"name"=>"MyString1", "email"=>"MyText1"}}}
確かにJSON形式のファイルを取得できました。この中身を検証するためのテストを書きましょう。各キーに対する値が正しければ期待するJSONファイルが取得できているということを検証できます。
require 'rails_helper' RSpec.describe "Api::V1::Authentications", type: :request do describe "POST /api/v1/authentications" do let!(:user) { create(:user) } let(:password) { 'password' } context '正しいパラメータが入力された時' do it 'JSON形式の情報を返す' do request_hash = { headers: { CONTENT_TYPE: 'application/json', ACCEPT: 'application/json' }, params: { email: user.email, password: password }.to_json } post api_v1_authentication_path, request_hash expect(JSON.parse(response.body).dig('data', 'id').to_i).to eq(user.id) expect(JSON.parse(response.body).dig('data', 'attributes', 'name')).to eq(user.name) expect(JSON.parse(response.body).dig('data', 'attributes', 'email')).to eq(user.email) expect(response).to have_http_status(200) end end end end
dig
メソッドについて簡単に説明します。dig
メソッドはネストしたハッシュから安全に値を取り出すことができるメソッドです。例えば、expect(JSON.parse(response.body).dig('data', 'id')
の部分ですが、dig('data', 'id')
とすることで、最初のキーである'data'の値のハッシュのキーである'id'の値である'1'を取り出しています。
{"data"=>{"id"=>"1", "type"=>"user", "attributes"=>{"name"=>"MyString1", "email"=>"MyText1"}}}
ようは、深い階層にあたるハッシュの値を取得するメソッドということになります。下記2つの文もそれぞれ深い階層にある値を取り出し、その値が期待しているものかを検証しているのです。
expect(JSON.parse(response.body).dig('data', 'attributes', 'name')).to eq(user.name) expect(JSON.parse(response.body).dig('data', 'attributes', 'email')).to eq(user.email)
テストを実行するとパスするかと思います。
終わりに
2つの記事に分けましたがやはりボリュームが大きかったですね。
リクエストスペックは1つ目の記事にも書きましたが、APIのための統合テストです。ですので、基本的にAPIを使用しない場合はメジャーなシステムスペックを使用しましょう!
以上、大ちゃんの駆け出し技術ブログでした!