最近、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.
記載の通りCommittee
はOpenAPIで定義したドキュメントをもとにリクエスト/レスポンスの形式を検証するミドルウェアを提供してくれます。
この提供されているミドルウェアを使うことでRspec等のテスティングフレームワークでで検証することができます🙌
Committeeの使い方
Committeeを導入する
Committee
をRailsで使う場合には良い感じにしてくれているcommittee-rails
というgemがあるので、今回はそちらを使いました💎
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