Skip to content

Generate OpenAPI schema from RSpec request specs

License

Notifications You must be signed in to change notification settings

degica/rspec-openapi

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

rspec-openapi test

Generate OpenAPI schema from RSpec request specs.

What's this?

There are some gems which generate OpenAPI specs from RSpec request specs. However, they require a special DSL specific to these gems, and we can't reuse existing request specs as they are.

Unlike such existing gems, rspec-openapi can generate OpenAPI specs from request specs without requiring any special DSL. Furthermore, rspec-openapi keeps manual modifications when it merges automated changes to OpenAPI specs in case we can't generate everything from request specs.

Installation

Add this line to your application's Gemfile:

gem 'rspec-openapi', group: :test

Usage

Run rspec with OPENAPI=1 to generate doc/openapi.yaml for your request specs.

$ OPENAPI=1 bundle exec rspec

Example

Let's say you have a request spec like this:

RSpec.describe 'Tables', type: :request do
  describe '#index' do
    it 'returns a list of tables' do
      get '/tables', params: { page: '1', per: '10' }, headers: { authorization: 'k0kubun' }
      expect(response.status).to eq(200)
    end

    it 'does not return tables if unauthorized' do
      get '/tables'
      expect(response.status).to eq(401)
    end
  end

  # ...
end

If you run the spec with OPENAPI=1,

OPENAPI=1 rspec spec/requests/tables_spec.rb

It will generate doc/openapi.yaml file like:

openapi: 3.0.3
info:
  title: rspec-openapi
paths:
  "/tables":
    get:
      summary: index
      tags:
      - Table
      parameters:
      - name: page
        in: query
        schema:
          type: integer
        example: 1
      - name: per
        in: query
        schema:
          type: integer
        example: 10
      responses:
        '200':
          description: returns a list of tables
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    id:
                      type: integer
                    name:
                      type: string
                    # ...

and the schema file can be used as an input of Swagger UI or Redoc.

Redoc example

Configuration

The following configurations are optional.

require 'rspec/openapi'

# Change the path to generate schema from `doc/openapi.yaml`
RSpec::OpenAPI.path = 'doc/schema.yaml'

# Change the output type to JSON
RSpec::OpenAPI.path = 'doc/schema.json'

# Or generate multiple partial schema files, given an RSpec example
RSpec::OpenAPI.path = -> (example) {
  case example.file_path
  when %r[spec/requests/api/v1/] then 'doc/openapi/v1.yaml'
  when %r[spec/requests/api/v2/] then 'doc/openapi/v2.yaml'
  else 'doc/openapi.yaml'
  end
}

# Disable generating `example`
RSpec::OpenAPI.enable_example = false

# Change `info.version`
RSpec::OpenAPI.application_version = '1.0.0'

# Set the info header details
RSpec::OpenAPI.info = {
  description: 'My beautiful API',
  license: {
    'name': 'Apache 2.0',
    'url': 'https://www.apache.org/licenses/LICENSE-2.0.html'
  }
}

# Set request `headers` - generate parameters with headers for a request
RSpec::OpenAPI.request_headers = %w[X-Authorization-Token]

# Set response `headers` - generate parameters with headers for a response
RSpec::OpenAPI.response_headers = %w[X-Cursor]

# Set `servers` - generate servers of a schema file
RSpec::OpenAPI.servers = [{ url: 'http://localhost:3000' }]

# Set `security_schemes` - generate security schemes
RSpec::OpenAPI.security_schemes = {
  'MyToken' => {
    description: 'Authenticate API requests via a JWT',
    type: 'http',
    scheme: 'bearer',
    bearerFormat: 'JWT',
  },
}

# Generate a comment on top of a schema file
RSpec::OpenAPI.comment = <<~EOS
  This file is auto-generated by rspec-openapi https://github.com/k0kubun/rspec-openapi

  When you write a spec in spec/requests, running the spec with `OPENAPI=1 rspec` will
  update this file automatically. You can also manually edit this file.
EOS

# Generate a custom description, given an RSpec example
RSpec::OpenAPI.description_builder = -> (example) { example.description }

# Change the example type(s) that will generate schema
RSpec::OpenAPI.example_types = %i[request]

Can I use rspec-openapi with $ref to minimize duplication of schema?

Yes, rspec-openapi v0.7.0+ supports $ref mechanism and generates schemas under #/components/schemas with some manual steps.

  1. First, generate plain OpenAPI file.
  2. Then, manually replace the duplications with $ref.
paths:
  "/users":
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/User"
  "/users/{id}":
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
# Note) #/components/schamas is not needed to be defined.
  1. Then, re-run rspec-openapi. It will generate #/components/schemas with the referenced schema (User for example) newly-generated or updated.
paths:
  "/users":
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/User"
  "/users/{id}":
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/User"
components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        role:
          type: array
          items:
            type: string

rspec-openapi also supports $ref in properties of schemas. Example)

paths:
  "/locations":
    get:
      responses:
        '200':
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Location"
components:
  schemas:
    Location:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        Coordinate:
          "$ref": "#/components/schemas/Coordinate"
    Coordinate:
      type: object
      properties:
        lat:
          type: string
        lon:
          type: string

Note that automatic schemas update feature is still new and may not work in complex scenario. If you find a room for improvement, open an issue.

How can I add information which can't be generated from RSpec?

rspec-openapi tries to keep manual modifications as much as possible when generating specs. You can directly edit doc/openapi.yaml as you like without spoiling the automatic generation capability.

Can I exclude specific specs from OpenAPI generation?

Yes, you can specify openapi: false to disable the automatic generation.

RSpec.describe '/resources', type: :request, openapi: false do
  # ...
end

# or

RSpec.describe '/resources', type: :request do
  it 'returns a resource', openapi: false do
    # ...
  end
end

Customizations

Some examples' attributes can be overwritten via RSpec metadata options. Example:

  describe 'GET /api/v1/posts', openapi: {
    summary: 'list all posts',
    description: 'list all posts ordered by pub_date',
    tags: %w[v1 posts],
    security: [{"MyToken" => []}],
  } do
    # ...
  end

NOTE: description key will override also the one provided by RSpec::OpenAPI.description_builder method.

Experimental minitest support

Even if you are not using rspec this gem might help you with its experimental support for minitest.

Example:

class TablesTest < ActionDispatch::IntegrationTest
  openapi!

  test "GET /index returns a list of tables" do
    get '/tables', params: { page: '1', per: '10' }, headers: { authorization: 'k0kubun' }
    assert_response :success
  end

  test "GET /index does not return tables if unauthorized" do
    get '/tables'
    assert_response :unauthorized
  end

  # ...
end

It should work with both classes inheriting from ActionDispatch::IntegrationTest and with classes using Rack::Test directly, as long as you call openapi! in your test class.

Please note that not all features present in the rspec integration work with minitest (yet). For example, custom per test case metadata is not supported. A custom description_builder will not work either.

Links

Existing RSpec plugins which have OpenAPI integration:

Acknowledgements

License

The gem is available as open source under the terms of the MIT License.

About

Generate OpenAPI schema from RSpec request specs

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Ruby 89.8%
  • HTML 7.4%
  • JavaScript 1.7%
  • Other 1.1%