No RSpec, No rswag. Minitest FTW!

11 February 2026
28 views
6 mins read

TL;DR

Stop writing API documentation by hand. Stop letting it get stale. Let your tests — the things you actually maintain — be the source of truth. openapi_minitest makes that stupid simple.

Check it out on GitHub.

If you're anything like me, you've been through the API documentation cycle more times than you'd like to admit. You know the one: build the API, swear you'll document it this time, ship it without docs, get yelled at, then spend a weekend writing YAML by hand like some kind of medieval scribe.

But before I tell you what I built, let me tell you what pushed me over the edge.

The RSpec Breakup

I used RSpec for years. Everyone does. It's the default. You rails new, you add rspec-rails, you write your describe blocks...

But at some point I started asking myself: what is RSpec actually giving me? It's a whole parallel universe of matchers, contexts, let blocks, subject, shared examples, and a DSL that — let's be honest — exists mostly to make tests read like English sentences that no English speaker would ever actually say. Under the hood, it's just assertions. Minitest does assertions. Ruby does assertions. RSpec is a dependency that wraps something Ruby already has, adds a learning curve, adds upgrade headaches, and adds config files. Every major Rails upgrade, there's RSpec catching up. Every new Ruby version, you're checking if your rspec-rails version is compatible yet.

So I dropped it. Switched to Minitest. It ships with Ruby, it ships with Rails, it's fast, it's simple, and when something breaks, the stack trace points at your code instead of 47 layers of DSL metaprogramming. No regrets.

But then I needed API documentation.

The Minitest Documentation Gap

RSpec has rswag. Say what you will about RSpec, but rswag is genuinely good — you write spec-like DSL, it generates Swagger/OpenAPI docs, it even gives you a UI. It's one of those "it just works" gems.

Minitest has... nothing. Seriously, I looked. I googled. I asked around. There are a few abandoned repos with 12 stars from 2017. There's nothing actively maintained, nothing that generates modern OpenAPI 3.1, nothing that feels like it belongs in a real production app.

So I built one.

openapi_minitest generates OpenAPI 3.1 documentation straight from your Minitest integration tests. No separate DSL to learn. No annotation comments littering your controllers. Just one method call: document_response.

The Pitch in 10 Seconds

You already write integration tests. Those tests already hit your endpoints, send params, and check responses. All that information — the HTTP method, the path, the request body, the response — it's right there. Why are we writing it down twice?

openapi_minitest just listens. You tell it "hey, document this one" and it does the rest.

What It Looks Like

class UsersApiTest < ActionDispatch::IntegrationTest
  def test_returns_users
    get "/api/users", headers: auth_headers

    assert_response 200
    document_response schema: :UserList, description: "Returns all users"
  end

  def test_creates_user
    post "/api/users",
      params: { user: { name: "Jane", email: "jane@example.com" } },
      headers: auth_headers,
      as: :json

    assert_response 201
    document_response schema: :User, tags: ["Users"]
  end
end

That's it. That's the whole integration. One line per endpoint you want documented. Your test still does its job — asserting behavior, catching regressions. It just also produces documentation now.

Run your tests with OPENAPI_GENERATE=true and you get a complete OpenAPI 3.1 YAML file. A real one, with schemas, examples, path parameters, security schemes — the works.

"But What About Schemas?"

Fair question. You define them once, up front:

OpenapiMinitest.define_schema :User, {
  type: :object,
  properties: {
    id: { type: :integer },
    email: { type: :string, format: :email },
    name: { type: :string }
  },
  required: %w[id email]
}

And then you reference them by name: document_response schema: :User. The gem handles turning that into a proper $ref in the output.

You can also turn on schema validation so your tests fail if your API response doesn't match the schema you said it would. Your docs and your code literally cannot drift apart. Enable strict: true and it'll even catch extra fields your schema doesn't know about.

document_response schema: :User, strict: true
# => "Nope, your response has an 'avatar_url' field you didn't document. Fix it."

Documentation-driven development, but backwards. And it actually works.

The Smart Bits

Multiple examples per endpoint. Every test that hits the same endpoint with the same status code becomes a separate example in the docs. Your test_returns_users and test_filters_users_by_name both contribute to the GET /api/users200 documentation. Consumers of your API get to see multiple realistic scenarios.

Request body capture. POST a JSON body in your test? It shows up as an example in the generated requestBody. Because of course it should.

Browse Your Docs with Scalar

If you're on Rails, there's a generator:

rails generate openapi_minitest:install

This drops in a controller, a view, and routes that serve your docs through Scalar — a modern, pretty API reference UI. Hit /api-docs in your browser and you're looking at your fully interactive API documentation. Try-it-out buttons and everything.

Why Not Just Stay on RSpec for the Docs?

I can already hear it: "Why not just keep RSpec for your API tests and use rswag?"

Because that defeats the entire point. I left RSpec to reduce dependencies and complexity, not to maintain two test frameworks side by side so one of them can generate YAML for me. That's the kind of compromise that sounds reasonable in a meeting and makes you miserable six months later.

openapi_minitest exists so you don't have to choose between a simple test stack and having API docs. The surface area is tiny — one config block, one define_schema call per type, one document_response per endpoint. No DSL gymnastics, no new syntax to learn. If you can write a Minitest integration test, you already know 95% of what you need.

Also, it generates OpenAPI 3.1.0, not 3.0. Which means proper null type support, JSON Schema alignment, and no more nullable: true hacks.

Getting Started

# Gemfile
gem "openapi_minitest"
# test/support/openapi.rb
OpenapiMinitest.configure do |config|
  config.title = "My API"
  config.version = "1.0.0"
  config.output_path = "doc/openapi.yml"
  config.validate_schema = true
end
OPENAPI_GENERATE=true rails test test/integration/
# => doc/openapi.yml has entered the chat

That's three steps from zero to auto-generated API docs. The hardest part is writing good integration tests, and you should be doing that anyway.

Example of Scalar doc for Vinyl