Miniswag
OpenAPI (Swagger) documentation DSL for Minitest. A port of rswag that works with Minitest instead of RSpec.
Write API integration tests that simultaneously validate your API responses and generate OpenAPI 3.x specification files — no RSpec required.
Gems
| Gem | Description |
|---|---|
miniswag |
Core DSL and OpenAPI spec generator (replaces rswag-specs) |
miniswag-api |
Rails engine that serves OpenAPI files as JSON/YAML endpoints (replaces rswag-api) |
miniswag-ui |
Rails engine that serves Swagger UI powered by your OpenAPI specs (replaces rswag-ui) |
Installation
Add to your Gemfile:
# Core — test DSL + spec generation
gem "miniswag", group: :test
# Optional — serve specs and Swagger UI in your app
gem "miniswag-api"
gem "miniswag-ui"Then run:
bundle install
rails generate miniswag:install # creates test/openapi_helper.rb
rails generate miniswag:api:install # creates config/initializers/miniswag_api.rb
rails generate miniswag:ui:install # creates config/initializers/miniswag_ui.rbQuick Start
1. Configure your OpenAPI specs
Edit test/openapi_helper.rb:
require "miniswag"
Miniswag.configure do |config|
config.openapi_root = Rails.root.join("docs/api").to_s
config.openapi_specs = {
"v1.yaml" => {
openapi: "3.0.1",
info: { title: "My API", version: "v1" },
servers: [{ url: "https://api.example.com" }]
}
}
end2. Write a test
require "openapi_helper"
class PetsTest < Miniswag::TestCase
path "/pets" do
get "Lists all pets" do
tags "Pets"
produces "application/json"
response 200, "successful" do
schema type: :array, items: { "$ref" => "#/components/schemas/Pet" }
run_test!
end
end
post "Creates a pet" do
tags "Pets"
consumes "application/json"
parameter name: :body, in: :body, schema: {
type: :object,
properties: {
name: { type: :string },
age: { type: :integer }
},
required: %w[name]
}
response 201, "pet created" do
params { { body: { name: "Fido", age: 3 } } }
run_test!
end
response 422, "invalid request" do
params { { body: { age: -1 } } }
run_test!
end
end
end
end3. Generate OpenAPI specs
rake miniswag:swaggerizeThis runs your tests and writes the resulting OpenAPI files to openapi_root.
DSL Reference
The DSL mirrors rswag closely. Key differences from rswag are noted below.
Structure
class MyTest < Miniswag::TestCase
openapi_spec "admin.yaml" # target a specific spec file
path "/resources/{id}" do
get "Fetch a resource" do
response 200, "success" do
run_test!
end
end
end
endParameters
parameter name: :id, in: :path, type: :integer
parameter name: :status, in: :query, type: :string
parameter name: "X-Request-Id", in: :header, type: :string
parameter name: :body, in: :body, schema: { type: :object, properties: { ... } }Path parameters are automatically marked required: true.
Providing parameter values
Instead of rswag's let blocks, use params:
response 200, "success" do
params { { id: @resource.id, status: "active" } }
run_test!
endSetup (replacing let! / before)
response 200, "success" do
before { @resource = create_resource(name: "test") }
params { { id: @resource.id } }
run_test!
endCustom assertions after the request
run_test! do |response|
data = JSON.parse(response.body)
assert_equal "test", data["name"]
endOperation attributes
get "Fetch resource" do
tags "Resources"
operationId "getResource"
description "Returns a single resource by ID"
consumes "application/json"
produces "application/json"
security [{ bearer: [] }]
deprecated true
endResponse schema & headers
response 200, "success" do
schema type: :object, properties: { id: { type: :integer } }
header "X-Rate-Limit", type: :integer, description: "Requests per hour"
run_test!
endRequest body examples
post "Create resource" do
request_body_example value: { name: "example" }, summary: "Basic example"
# ...
endMetadata (custom extensions)
response 200, "success" do
metadata[:operation] ||= {}
metadata[:operation]["x-public-docs"] = true
run_test!
endMigrating from rswag
| rswag (RSpec) | miniswag (Minitest) |
|---|---|
RSpec.describe "...", type: :request do |
class MyTest < Miniswag::TestCase |
let(:Authorization) { "Bearer ..." } |
params { { Authorization: "Bearer ..." } } |
let!(:resource) { create(:resource) } |
before { @resource = create(:resource) } |
let(:id) { resource.id } |
include in params block |
let(:body) { { name: "x" } } |
include in params block |
openapi_spec: metadata on describe |
openapi_spec "name.yaml" at class level |
require "swagger_helper" |
require "openapi_helper" |
Configuration
Miniswag.configure do |config|
# Required — where to write generated spec files
config.openapi_root = Rails.root.join("docs/api").to_s
# Required — spec definitions (supports multiple files)
config.openapi_specs = { "v1.yaml" => { openapi: "3.0.1", info: { ... } } }
# Optional — output format (:json or :yaml, default :yaml)
config.openapi_format = :yaml
# Optional — strict schema validation (rejects additional properties)
config.openapi_strict_schema_validation = false
endRequirements
- Ruby >= 3.1
- Rails >= 7.0, < 9.0
- Minitest ~> 5.0
License
MIT. See MIT-LICENSE.