Rails + Committeeで書いたテストコードが TypeError: no implicit conversion of String into Integer で落ちたため対応してみた

OpenAPIスキーマのあるRailsAPIサーバにて、テストコードを Committee + Committer::Rails で書いていたところ、 TypeError: no implicit conversion of String into Integer というエラーが出ました。

エラーを解消するまでに少々悩んだため、メモを残します。

 
目次

 

環境

 

エラーを再現するための環境構築

前回の記事で使用したサーバアプリを元に、環境構築を行います。

 

作成するAPIの仕様

curl

curl 'http://localhost:3001/api/schema/array/items/shops/1'

とリクエストすると、

{"apples":[{"name":"シナノゴールド","color":"黄"},{"name":"ふじ","color":"赤"}]}

というレスポンスが返ってくるようなAPIを作成します。

 

OpenAPIスキーマ

まずはOpenAPIを作成します。

前回の記事のOpenAPIスキーマを流用しますが、適宜 componentsparametersschemas を利用しています。

openapi: 3.0.0
info:
  title: Rails with OpenAPI
  version: 0.0.1
servers:
  - url: http://localhost:3001
components:
  schemas:
    Apple:
      type: object
      properties:
        name:
          type: string
          example: シナノゴールド
        color:
          type: string
          example:parameters:
    ShopID:
      name: shopId
      in: path
      description: 店舗ID
      required: true
      schema:
        type: integer

paths:
  /api/schema/array/items/shops/{shopId}:
    get:
      parameters:
        - $ref: '#/components/parameters/ShopID'
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                type: object
                properties:
                  apples:
                    type: array
                    items:
                      - $ref: '#/components/schemas/Apple'

 

コントローラ

続いてコントローラを作成します。

URL末尾が shopId ど動的になっているため、 show メソッドで定義します。

なお、今回モデルやDBの準備は省略したいので、paramsに含まれる id は無視し、常に同じレスポンスを返すようにしています。

class Api::Schema::Array::Items::ShopsController < ApplicationController
  def show
    # parameterのidは使わない
    render json: {
      apples: [
        { name: 'シナノゴールド', color: '' },
        { name: 'ふじ', color: '' },
      ],
    }
  end
end

 

ルーティング

仕様に合わせたルーティングを作成します。

Rails.application.routes.draw do
  namespace :api do
    namespace :schema do
      namespace :array do
        namespace :items do
          resources :shops, only: [:show]
        end
      end
    end
  end
end

 

curlで動作確認

% curl 'http://localhost:3001/api/schema/array/items/shops/1'

とすると、

{"apples":[{"name":"シナノゴールド","color":"黄"},{"name":"ふじ","color":"赤"}]}

が返ってきました。仕様通り作成できました。

 

テストコードの作成と実行

続いて Committee を使ってテストコード /spec/requests/api/schema/array/items/shops_controller_spec.rb を作成します。

require 'rails_helper'

RSpec.describe 'Api::Schema::Array::ItemsController', type: :request do
  let(:response_body) { JSON.parse(response.body) }

  describe 'GET /api/schema/array/items/shops/{shopId}' do
    it '正常系' do
      get '/api/schema/array/items/shops/1'

      assert_request_schema_confirm
      assert_response_schema_confirm(200)
    end
  end
end

 
テストを実行すると、エラーで落ちました。

% bundle exec rspec spec/requests/api/schema/array/items/shops_controller_spec.rb
F

Failures:

  1) Api::Schema::Array::Items::ShopsController GET /api/schema/array/items/shops/{shopId} 正常系
     Failure/Error: assert_request_schema_confirm
     
     TypeError:
       no implicit conversion of String into Integer

 

原因

no implicit conversion of String into Integer とあることから、当初はOpenAPIスキーマpath で指定した shopId が誤っていることを疑いました。

しかし、それらしい原因が分かりませんでした。

 
続いて、OpenAPIスキーマの仕様を見ていったところ、DataType Arrayのところで目が止まりました。
Arrays | specification | Data Types

 
Arrayの定義をよく見ると

type: array
items:
  type: string

となっています。

また、 $refs を使う場合は

type: array
items:
  $ref: '#/components/schemas/Pet'

となっています。

 

一方、手元のOpenAPIスキーマをよく見ると

apples:
  type: array
  items:
    - $ref: '#/components/schemas/Apple'

と、 $refs の先頭に - が付いています。

どうやら、 items の定義方法が誤っていたようです。

 
なぜ items の先頭に - が付けてしまったのかを考えたところ、 Mixed-Type Arraysのところで oneOf を使った定義を見つけました。この定義をどこかで見かけたために - を先頭につけてしまったのかもしれません。
https://swagger.io/docs/specification/data-models/data-types/#mixed-array

type: array
items:
  oneOf:
    - $ref: '#/components/schemas/Cat'
    - $ref: '#/components/schemas/Dog'

 

また、Mixed-Type Arraysで誤った例として

# Incorrect
items:
  - type: string
  - type: integer

# Incorrect as well
items:
  type: 
    - string
    - integer

も載っていました。こちらも見落としていたようです。

 

対応

items の定義を修正します。

/api/schema/array/items/shops/{shopId}:
  get:
    summary: OpenAPIスキーマのArrayに関する動作検証用のAPI
    parameters:
      - $ref: '#/components/parameters/ShopID'
    responses:
      '200':
        description: successful operation
        content:
          application/json:
            schema:
              type: object
              properties:
                apples:
                  type: array
                  items:
                    # 修正前
                    # - $ref: '#/components/schemas/Apple'
                    # 修正後
                    $ref: '#/components/schemas/Apple'

 
テストコードを流すと、テストがパスしました。

% bundle exec rspec spec/requests/api/schema/array/items/shops_controller_spec.rb
.

Finished in 0.0461 seconds (files took 0.99509 seconds to load)
1 example, 0 failures

 

余談:他のツールではどのように見えるか

RubyMineの OpenAPI Specifications の場合

Rubyでの開発中にOpenAPIスキーマを書くときは、RubyMineの OpenAPI Specifications プラグインを使っています。
OpenAPI Specifications - IntelliJ IDEs Plugin | Marketplace

 
OpenAPIスキーマを書くときもコードジャンプができたり、補完入力ができるためです。

例えば、こんな感じで $refs の途中まで入力するとサジェストされます。

 
サジェストされたものをEnterで確定すると、 $refs の設定値が自動的に補完・入力されます。

 
ただ、このプラグインを使っていてもエラーが検出できていないっぽいです。

OpenAPIスキーマのプレビューを見たところ、type: object ではなく type: string で認識されているようでした。

 
一方、正しく書いたOpenAPIスキーマの場合は、 type: object で認識されているようです。

 

Swagger Editor (online) の場合

OpenAPIスキーマのエディタとして、オンラインのエディタも提供されています。
https://editor.swagger.io/

 
そこで、誤っている定義を貼り付けてみたところ、エラーが出ていました。

 
 

Swagger Editor をローカルで動かした場合

Swagger Editorでバリデーションするのは良さそうですが、もし仕事で使う場合はオンラインなのがネックです。

そのため、Swagger Editor をローカルで動かしてみて、同じバリデーションが走るかどうかを確認してみます。
Download OpenAPI Editor | Swagger Open Source

 
GithubのREADMEを読むと、公式でDockerイメージが提供されているようですので、そちらを使って起動してみます。
https://github.com/swagger-api/swagger-editor#running-the-image-from-dockerhub

# イメージをダウンロード
% docker pull swaggerapi/swagger-editor

# ローカルの20030 ポートで起動
% docker run -d -p 20030:8080 swaggerapi/swagger-editor

 
20030ポートを開き、先ほどエラーになったOpenAPIスキーマを貼り付けると、無事にエラーとなりました。

 
これにより、OpenAPI Specifications + Swagger Editor (ローカル) で開発できる環境ができました。

 

ソースコード

Githubに上げました。
https://github.com/thinkAmi-sandbox/rails_7_0_openapi_server_app

プルリクはこちら。
https://github.com/thinkAmi-sandbox/rails_7_0_openapi_server_app/pull/2