はじめに
イタンジ株式会社で物件管理くんの開発をしている三島です。
物件管理くんでは、RailsによるAPIサーバを採用しており、テストにはRSpecを利用しています。 本記事ではOpen APIとCommitteeを使用し、RSpecでAPIテストを行う方法について記載します。
Committee
Committeeは、OpenAPIで定義したスキーマに基づいて、アプリケーションのリクエストとレスポンスの検証を行うミドルウェアを提供してくれます。
Committee::RailsはCommitteeのラッパーライブラリでrailsへの導入を容易にし、OpenAPIの仕様の保証、開発の整合性と品質を向上させるのに役立ちます。
導入方法
1.Gemfileにcommittee-rails
を追加し、$ bundle install
を実行します。
gem 'committee-rails'
2.RSpecで利用できるようにrails-helper.rb
に以下の記述を追加します。
schema_pathには利用したいファイルのパスを記載してください。
RSpec.configure do |config| config.include Committee::Rails::Test::Methods config.add_setting :committee_options config.committee_options = { schema_path: Rails.root.join('etc/docs/openapi.yml'), }
上記の設定だけでRSpecのrequest specでassert_response_schema_confirm
などの検証メソッドが利用できるようになります。
https://github.com/willnet/committee-rails#normal-usage
RSpecによるAPIテスト
以下のようなスキーマが定義された投稿を扱うPostモデルを取得するエンドポイントでcommittee-railsを利用したレスポンスの検証を考えてみます。
- OpenAPIの定義
openapi: 3.0.0 paths: /api/v1/posts/{id}: get: summary: 投稿の情報 parameters: - in: path name: id required: true schema: type: integer responses: '200': description: 投稿の取得 content: application/json: schema: $ref: '#/components/schemas/Post' components: schemas: Post: type: object properties: id: type: integer title: type: string required: - id - title additionalProperties: false
- controller
module Api module V1 class PostsController < ApplicationController def show @post = Post.find(params[:id]) render json: @post end end end end
- request spec
RSpec.describe Api::V1::PostsController, type: :request do describe '#show' do subject(:request) { get api_v1_post_path(post) } let(:post) { create(:post) } it do request assert_response_schema_confirm(200) end end end
こちらでテストを実行すると正常系の検証を成功させることができます。
assert_response_schema_confirm
は引数にステータスコードを設定でき、レスポンスと指定したステータスコードにリクエストの結果がなっているかを検証できます。
以下のように、正常系の検証の箇所でassert_response_schema_confirm(400)
とすると、本来は200になる箇所が400を想定したテストになるので、テストは失敗します。
F Failures: 1) Api::V1::PostsController#show Failure/Error: assert_response_schema_confirm(400) Committee::InvalidResponse: Expected `400` status code, but it was `200`. # /usr/local/bundle/gems/committee-5.0.0/lib/committee/test/methods.rb:32:in `assert_response_schema_confirm' # ./spec/requests/api/v1/posts_controller_spec.rb:9:in `block (3 levels) in <top (required)>' Finished in 0.08364 seconds (files took 2.86 seconds to load) 1 example, 1 failure Failed examples: rspec ./spec/requests/api/v1/posts_controller_spec.rb:7 # Api::V1::PostsController#show
適切にレスポンスを検証するためのOpenAPI定義
committee-railsによる検証を適切に、より厳格に行うには以下の2つが重要です。
OpenAPI定義で必須項目は
required
を指定する。required
を指定しないpropertiesはレスポンスに含まれているかどうか検証されず、テストをパスしてしまいます。 なので、検証したい必須な要素はrequired
を指定するようにしましょう。
additionalProperties: false
を設定する。additionalProperties: false
とrequired
を組み合わせることでOpenAPIに定義されていない値がレスポンスに含まれているかを検知できるようになります。 これにより、本来レスポンスに含めるべきではない情報を誤って返すことなどを未然に防ぐことができ、より厳格にレスポンスを検証できるようになります。
先ほどのPostモデルでcontentという要素を扱うようにして、レスポンスは変わらずidとtitleのみが定義されている状態を考えます。 現在のcontrollerでは取得した@postをそのままrenderしているので、レスポンスにはid, title, contentが含まれます。
この状態でテストを実行すると以下のようになります。
1) Api::V1::PostsController#show Failure/Error: assert_response_schema_confirm(200) Committee::InvalidResponse: #/components/schemas/Post does not define properties: content # /usr/local/bundle/gems/committee-5.0.0/lib/committee/schema_validator/open_api_3/operation_wrapper.rb:39:in `rescue in validate_response_params' # /usr/local/bundle/gems/committee-5.0.0/lib/committee/schema_validator/open_api_3/operation_wrapper.rb:34:in `validate_response_params' # /usr/local/bundle/gems/committee-5.0.0/lib/committee/schema_validator/open_api_3/response_validator.rb:20:in `call' # /usr/local/bundle/gems/committee-5.0.0/lib/committee/schema_validator/open_api_3.rb:41:in `response_validate' # /usr/local/bundle/gems/committee-5.0.0/lib/committee/test/methods.rb:40:in `assert_response_schema_confirm' # ./spec/requests/api/v1/posts_controller_spec.rb:9:in `block (3 levels) in <top (required)>' # ------------------ # --- Caused by: --- # OpenAPIParser::NotExistPropertyDefinition: # #/components/schemas/Post does not define properties: content # /usr/local/bundle/gems/openapi_parser-1.0.0/lib/openapi_parser/schema_validator.rb:63:in `validate_data'
スキーマではcontentは定義されていませんが、実際のレスポンスにはcontentが含まれているため、スキーマと実装の不一致を検知して、意図通りにテストが失敗することを確認できました。
それでは、スキーマ定義に以下のようにcontentを追加して再度テストを実行します。
components: schemas: Post: type: object properties: id: type: integer title: type: string content: type: string required: - id - title - content additionalProperties: false
Randomized with seed 43980 . Finished in 0.07156 seconds (files took 2.41 seconds to load) 1 example, 0 failures
スキーマと実装の乖離がなくなり、テストを通すことが出来ました。
導入してみて
OpenAPIとCommitteeを利用したテストの仕組みを導入したことで新たに以下の点で効果があったと思います。
スキーマと実装の一致を保証
- committee-railsを使用することで、スキーマと実際のAPIレスポンスの乖離を検出することができるようになりました。 これにより、スキーマの信頼性を高めることができ、スキーマベースでの開発、コミュニケーションができるようになりました。
テストの強化
- スキーマに基づいたテストを行うことで、APIの仕様変更があった際のテストの更新が容易になりました。
ドキュメントとしての価値
- OpenAPIスキーマは、APIのドキュメントとしても機能し、APIの利用方法をスキーマで理解できるようになりました。
まとめ
今回はOpenAPIとCommitteeによるテストについて書かせてもらいました。 committee-railsを利用することでテストの開発体験の向上、スキーマと実装の乖離を減らす仕組みを持たせることが出来ました。 この記事がどなたかの参考になれば幸いです。ありがとうございました。