OpenapiMinitest
Generate OpenAPI 3.1 documentation from your Minitest integration tests. No DSL, no magic - just one helper method.
Installation
Add to your Gemfile:
gem "openapi_minitest"Then run:
bundle installQuick Start
1. Configure (optional)
# config/initializers/openapi_minitest.rb (Rails)
# or test/support/openapi.rb
OpenapiMinitest.configure do |config|
config.title = "My API"
config.version = "1.0.0"
config.description = "API documentation"
config.output_path = "doc/openapi.yml" # YAML by default
config.servers = ["https://api.example.com"]
end2. Define Schemas
OpenapiMinitest.define_schema :User, {
type: :object,
properties: {
id: { type: :integer },
email: { type: :string, format: :email },
name: { type: :string }
},
required: %w[id email]
}
OpenapiMinitest.define_schema :UserList, {
type: :object,
properties: {
data: { type: :array, items: { "$ref" => "#/components/schemas/User" } },
meta: {
type: :object,
properties: {
total: { type: :integer },
page: { type: :integer }
}
}
}
}
OpenapiMinitest.define_schema :Error, {
type: :object,
properties: {
error: { type: :string },
code: { type: :integer }
}
}3. Write Tests
Write normal Minitest tests. Call document_response after each request you want documented:
class UsersApiTest < ActionDispatch::IntegrationTest
def test_returns_users
create(:user, name: "John")
create(:user, name: "Jane")
get "/api/users", headers: auth_headers
assert_response 200
document_response schema: :UserList, description: "Returns all users"
body = JSON.parse(response.body)
assert_equal 2, body["data"].size
end
def test_filters_users_by_name
create(:user, name: "John")
create(:user, name: "Jane")
get "/api/users", params: { q: "John" }, headers: auth_headers
assert_response 200
document_response schema: :UserList, description: "Filters users by name"
body = JSON.parse(response.body)
assert_equal 1, body["data"].size
end
def test_returns_single_user
user = create(:user, name: "John")
get "/api/users/#{user.id}", headers: auth_headers
assert_response 200
document_response schema: :User, description: "User found", tags: ["Users"]
end
def test_user_not_found
get "/api/users/999999", headers: auth_headers
assert_response 404
document_response schema: :Error, description: "User not found"
end
def test_creates_user
post "/api/users",
params: { user: { name: "New User", email: "new@example.com" } },
headers: auth_headers,
as: :json
assert_response 201
document_response schema: :User, description: "User created", tags: ["Users"]
end
def test_create_user_validation_error
post "/api/users",
params: { user: { name: "" } },
headers: auth_headers,
as: :json
assert_response 422
document_response schema: :Error, description: "Validation failed"
end
private
def auth_headers
{ "Authorization" => "Bearer #{generate_token}" }
end
end4. Generate Documentation
# Run tests with documentation generation
OPENAPI_GENERATE=true rails test test/integration/
# Or use the rake task
rails openapi:generate5. Browse Documentation (optional)
Run the install generator to add an API docs page powered by Scalar:
rails generate openapi_minitest:installThis creates:
-
app/controllers/api_docs_controller.rb— serves the docs UI and the OpenAPI spec file -
app/views/api_docs/index.html.erb— Scalar API reference page (titled with your Rails app name) - Routes:
GET /api-docsandGET /openapi.yml
Visit /api-docs in your browser to explore your API documentation interactively.
API Reference
Configuration Options
OpenapiMinitest.configure do |config|
config.title = "My API" # API title
config.version = "1.0.0" # API version
config.description = "Description" # API description
config.output_path = "doc/openapi.yml" # Output file path (YAML by default)
config.servers = [ # Server URLs
"https://api.example.com",
{ url: "https://staging.example.com", description: "Staging" }
]
config.security_schemes = { # Security schemes
bearer: {
type: :http,
scheme: :bearer
}
}
config.validate_schema = true # Validate responses against schemas
config.strict_validation = false # Fail if response contains undocumented fields
enddocument_response
Call after making a request to record it for documentation:
document_response(
schema: :SchemaName, # Schema reference (Symbol) or inline schema (Hash)
summary: "Operation summary", # Defaults to test name
description: "Response desc", # Description of this response
tags: ["Tag1", "Tag2"], # Tags for grouping
operation_id: "getUsers", # Unique operation ID
deprecated: false, # Mark endpoint as deprecated
strict: nil # Override strict validation (true/false/nil for global config)
)Schema Definition
# Reference to another schema
OpenapiMinitest.define_schema :UserList, {
type: :object,
properties: {
data: { type: :array, items: { "$ref" => "#/components/schemas/User" } }
}
}
# Inline schema in tests
document_response schema: {
type: :object,
properties: {
status: { type: :string }
}
}
# Nullable fields (OpenAPI 3.1 syntax - use type array instead of nullable: true)
OpenapiMinitest.define_schema :Article, {
type: :object,
properties: {
id: { type: :integer },
title: { type: :string },
subtitle: { type: [:string, :null] }, # nullable string
published_at: { type: [:string, :null], format: :datetime }
}
}Features
- No DSL - Just one helper method, write normal Minitest tests
- Schema validation - Optionally validate responses against schemas during tests
- Strict validation - Fail tests when responses contain undocumented fields
- Auto-detection - Automatically extracts path parameters, query params
- Security handling - Authorization headers automatically use configured security schemes
- Multiple examples - Each test becomes an example in the docs
- Request bodies - Captures POST/PUT/PATCH request bodies as examples
Strict Validation
Enable strict validation to catch undocumented fields in API responses. This helps ensure your documentation stays in sync with your implementation.
When strict mode is enabled, the test will fail if the response contains any fields not defined in the schema:
Response does not match schema:
The property '#/' contains additional properties ["unexpected_field"] outside of the schema when none are allowed
Global Configuration
OpenapiMinitest.configure do |config|
config.strict_validation = true # All schemas strictly validated
endPer-Call Override
# Force strict validation for this endpoint
document_response schema: :User, strict: true
# Disable strict validation for this endpoint (e.g., third-party responses)
document_response schema: :ExternalData, strict: false
# Use global config setting (default behavior)
document_response schema: :UserStrict validation works by automatically injecting additionalProperties: false into all object schemas during validation. This includes nested objects and objects within arrays. If a schema already defines additionalProperties, that value is preserved.
How It Works
- You write normal integration tests
- Call
document_responseafter requests you want documented - The gem captures:
- Request method, path, parameters, headers
- Response status, body
- Schema reference or definition
- After tests run, generates OpenAPI 3.1 YAML
Path Parameter Detection
The gem automatically detects numeric IDs in paths and converts them:
/api/users/123 -> /api/users/{user_id}
/api/users/123/posts/456 -> /api/users/{user_id}/posts/{post_id}
Security / Authentication
When you configure security_schemes and your tests include an Authorization header, the gem automatically adds the security property to those operations:
OpenapiMinitest.configure do |config|
config.security_schemes = {
bearer: {
type: :http,
scheme: :bearer
}
}
endOperations with Authorization headers will be generated with:
security:
- bearer: []Validator Compatibility
This gem generates OpenAPI 3.1.0 which supports type arrays for nullable fields:
type:
- string
- 'null'Some validators may not fully support OpenAPI 3.1.0 yet. If you encounter validation errors about type arrays, ensure your validator supports OpenAPI 3.1.0.
Non-Rails Usage
Include the DSL module manually:
class MyApiTest < Minitest::Test
include OpenapiMinitest::DSL
# ... your tests
end
# Generate documentation after tests
Minitest.after_run do
if ENV["OPENAPI_GENERATE"]
OpenapiMinitest::OpenAPI::Generator.new.write
end
endDevelopment
After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/k0va1/openapi_minitest.
License
MIT