Madogiwa Blog

主に技術系の学習メモに使っていきます。

CommitteeでOpenAPIによるAPI定義ドキュメントをもとにリクエスト/レスポンス形式をチェックする

最近、Nuxt.jsとRailsでSPAのアプリケーションを作っていて、schema管理をOpenAPIで行ってみているのですが、 せっかくなのでCommitteeを使ってCIで検証するようしてみたので導入方法とかをメモしておきます📝

Committeeとは?

CommitteeとはOpenAPIを使ったアプリケーション構築を支援してくれるGemのようです。

A collection of middleware to help build services with JSON Schema, OpenAPI 2, OpenAPI 3.

github.com

記載の通りCommitteeはOpenAPIで定義したドキュメントをもとにリクエスト/レスポンスの形式を検証するミドルウェアを提供してくれます。

この提供されているミドルウェアを使うことでRspec等のテスティングフレームワークでで検証することができます🙌

Committeeの使い方

Committeeを導入する

CommitteeRailsで使う場合には良い感じにしてくれているcommittee-railsというgemがあるので、今回はそちらを使いました💎

github.com

committeeと合わせてGemfileに記載してbundle installしてあげればOKです🙆‍♂️

gem 'committee'
gem 'committee-rails'

Committeeを使うための設定をする

Committeeを使うための設定はcommittee-railsのおかげでとても簡単です。

rails_helperに下記のような形で記載するだけです。

rails_helper.rb

  # configured for committee-rails
  config.add_setting :committee_options
  config.committee_options = {
    schema_path: Rails.root.join('schemas', 'api', 'v1', 'schema.yml').to_s,
    prefix: '/api/v1'
  }

schema_pathにはOpenAPIで定義ドキュメントへのファイルパス、prefixにはリクエスト時に付与するプレフィックス(api/v1等)を設定します。 ※prefixは設定しなくても大丈夫です。

Committeeを使ってテストする

先程の設定を行うことによりassert_schema_conformといったCommitteeのテスティングメソッドが使えるようになります。

実際のコード例は下記のような形になるかと思います、これで設定を行ってテストすることができました🙋‍♀️

require 'rails_helper'

describe Api::V1::FoodsController, type: :request do
  include Committee::Rails::Test::Methods

  describe 'GET /api/v1/foods' do
    before do
      create(:food, id: 1, name: 'name1', memo: 'memo1')
      create(:food, id: 2, name: 'name2', memo: 'memo2')
    end

    it 'confirm json schema' do
      get '/api/v1/foods'
      assert_request_schema_confirm
      assert_response_schema_confirm
    end

    it 'successed request' do
      get '/api/v1/foods'
      expect(response.status).to eq 200
    end
  end
end

おまけ: Committeeのテストを扱いやすくする(アイデアレベル)

こんな感じでshared_exampleにしておいても、ちょっと良いかなと思いました。

require 'rails_helper'

shared_examples 'Committer Schema Check For GET request_url' do
  it 'confirm json schema' do
    get request_url
    assert_request_schema_confirm
    assert_response_schema_confirm
  end
end

describe Api::V1::FoodsController, type: :request do
  include Committee::Rails::Test::Methods

  describe 'GET /api/v1/foods' do
    let(:request_url) { '/api/v1/foods' }

    before do
      create(:food, id: 1, name: 'name1', memo: 'memo1')
      create(:food, id: 2, name: 'name2', memo: 'memo2')
    end

    include_examples 'Committer Schema Check For GET request_url'

    it 'successed request' do
      get request_url
      expect(response.status).to eq 200
    end
  end
end

それかRails.routesから特定のnamespace配下のrouteを取得して自動的にテストしてもいいかもですね👀

require 'rails_helper'

module RoutingTestHelper
  def routes(namespace: nil, actions: nil, ignore_paths: [])
    filtered_routes = all_routes
    filtered_routes.select! { |route| route.name.include?(namespace) }    if namespace
    filtered_routes.select! { |route| actions.include?(route.action) }    if actions
    filtered_routes.select! { |route| ignore_paths.exclude?(route.name) } if ignore_paths
    filtered_routes.reject { |route| route.url.nil? }
  end

  private

  def rails_routes
    Rails.application.routes
  end

  def all_routes
    rails_routes.routes.select(&:name).map do |route|
      Route.new(route, rails_routes.url_helpers)
    end
  end

  class Route
    def initialize(route, url_helpers)
      @name = route.name
      @action = route.requirements[:action]
      @controller = route.defaults[:controller]
      @keys = route.path.required_names
      @url_helpers = url_helpers
    end

    attr_reader :name, :action, :controller, :keys, :url_helpers

    def attributes
      attributes = { host: 'localhost', controller: controller, action: action }
      # NOTE: must be created record with key before execution.
      attributes.tap { keys.each { |key| attributes.merge!(key.to_sym => 1) } }
    end

    def url
      url_helpers.url_for attributes
    rescue ActionController::UrlGenerationError
      nil
    end
  end
end

include RoutingTestHelper
include Committee::Rails::Test::Methods

RSpec.describe '画面に正常に遷移できるか確認', type: :request do
  IGNORE_PATHS = []
  before do
    # seed等を実行して前提データを作る
  end

  routes(actions: ['show', 'index'], ignore_paths: IGNORE_PATHS).each do |route|
    it "#{route.name}(#{route.url})" do
      get route.url
      assert_request_schema_confirm
      assert_response_schema_confirm
      expect(response.status).to eq 200
    end
  end
end

参考

qiita.com

qiita.com