February 23, 2026 · 11 min read
How to Test Your Rails SaaS: The Minimum Viable Test Suite for Shipping Fast in 2026
Jeronim Morina
Founder, Omaship
Most SaaS founders get testing completely wrong. Half of them write zero tests and deploy on a prayer. The other half write so many tests that adding a feature takes three times longer than it should. Both camps lose.
The founders who ship fast and sleep well have something in common: a minimum viable test suite. Not 100% coverage. Not TDD purism. A focused set of tests that catch the bugs that actually cost you customers and money.
This guide covers exactly what to test in a Rails SaaS, what to skip, and how to build a test suite that grows with your product instead of slowing it down. Written from the trenches of shipping a real Rails 8 SaaS, not from a testing textbook.
The testing spectrum: where most founders go wrong
There are two failure modes with testing, and they are equally destructive.
The under-tester
"I will add tests later." You will not. Later never comes, and now you have a codebase where every change is a gamble. You deploy on Friday afternoon and spend the weekend fixing production. Your users find the bugs before you do, and each incident erodes the trust you spent months building.
Under-testing is not a time-saving strategy. It is a deferred cost that compounds with interest. Every untested code path is a landmine waiting for the worst possible moment.
The over-tester
"Every method needs a unit test." Your test suite takes 20 minutes to run, half the tests are brittle mocks that break when you refactor, and adding a simple feature requires updating 15 test files. You spend more time maintaining tests than building product.
Over-testing kills velocity. When your test suite becomes a burden, developers start skipping it. Then you have the worst of both worlds: slow development and unreliable tests.
The sweet spot
The minimum viable test suite sits between these extremes. It covers the paths that matter -- authentication, billing, and your core value proposition -- and ignores the paths that do not. It runs fast, fails clearly, and gives you confidence to deploy without anxiety.
The goal is not to prove your code works. The goal is to catch the bugs that cost you customers. Test for business risk, not code coverage percentages.
What to test first: the priority stack
If you have zero tests and limited time, here is the order that gives you the most protection per hour invested. This is not theoretical. This is the order that catches the bugs that actually kill SaaS businesses.
1. Authentication and authorization
If your auth is broken, nothing else matters. A user who cannot log in churns immediately. A user who can access another user's data is a lawsuit waiting to happen.
- Sign up flow. User can create an account, receives confirmation, and lands in the right place.
- Login and logout. Valid credentials work, invalid credentials are rejected, sessions expire correctly.
- Password reset. The full flow: request reset, receive email, click link, set new password, log in with it.
- Authorization boundaries. User A cannot see or modify User B's data. This is the test that prevents your "we had a data breach" blog post.
With Rails 8's built-in authentication generator, you get a solid auth foundation. But the generator does not write your authorization tests. Those are on you, and they are non-negotiable.
2. Billing and subscription lifecycle
Billing bugs are the most expensive bugs in SaaS. A user who gets charged incorrectly will churn and leave a negative review. A user who gets free access because your webhook handler has a bug is revenue you will never recover.
- Subscription creation. User selects a plan, completes checkout, and gets the correct access level.
- Webhook processing. Stripe events (payment succeeded, payment failed, subscription canceled) are handled correctly.
- Plan changes. Upgrades and downgrades adjust access and billing immediately.
- Cancellation. User can cancel, access continues until period end, and the subscription actually ends.
- Failed payments. Grace periods work, dunning emails send, and access is revoked at the right time.
3. Core happy path
Whatever your SaaS does -- the one thing users pay you for -- that flow needs a test. Not every edge case. Just the happy path: the thing works when used as intended.
If you build a project management tool, test that a user can create a project, add tasks, and mark them complete. If you build an invoicing app, test that a user can create an invoice and send it. The core value proposition, end to end.
4. Data integrity
Model validations and database constraints that prevent bad data from entering your system. These are cheap to write and prevent expensive problems.
- Required fields are actually required
- Uniqueness constraints work (duplicate emails, duplicate slugs)
- Associations cascade correctly (deleting a user handles their data)
- Status transitions are valid (you cannot go from "canceled" to "active" without re-subscribing)
The minimum viable test suite: anatomy
A Rails SaaS test suite has three layers. Each serves a different purpose, and getting the balance right is what separates a test suite that helps from one that hinders.
Model tests: your safety net
Model tests are fast, focused, and cheap to write. They test your business logic in isolation -- validations, calculations, state transitions, and scopes.
- What to test: Validations, custom methods that contain business logic, scopes that filter important data, callbacks that have side effects.
- What to skip: Testing that ActiveRecord associations exist (Rails already tests this), testing trivial getters and setters, testing framework behavior.
A good rule of thumb: if a model method makes a decision or transforms data, test it. If it just delegates to a framework method, skip it.
Integration tests: your confidence layer
Integration tests (controller tests in Rails) verify that your endpoints work: correct responses, proper redirects, authentication enforcement, and parameter handling.
- What to test: Every controller action returns the expected status code. Authenticated routes reject unauthenticated users. Create and update actions persist data correctly. Destroy actions clean up properly.
- What to skip: Testing the exact HTML output (that is what system tests are for). Testing every validation error message. Testing framework-level behavior like CSRF protection.
Integration tests are the workhorses of your test suite. They run fast (no browser), test real request/response cycles, and catch the bugs that matter most: broken routes, missing authentication, and incorrect data handling.
System tests: your user's perspective
System tests drive a real browser and test your application the way a user experiences it. They are the slowest tests but catch things nothing else can: JavaScript bugs, Turbo integration issues, form submission flows, and cross-page interactions.
- What to test: Critical user journeys end to end. Sign up, complete onboarding, perform the core action, see the result. One system test per critical flow.
- What to skip: Everything you already covered with integration tests. System tests should not duplicate your integration test coverage. They should only test things that require a real browser.
The ratio matters. A healthy Rails SaaS test suite looks roughly like this:
| Test Type | Proportion | Speed | What it catches |
|---|---|---|---|
| Model tests | 40-50% | Milliseconds | Logic errors, validation gaps, bad state transitions |
| Integration tests | 35-45% | Fast (no browser) | Broken routes, auth failures, data persistence bugs |
| System tests | 10-20% | Seconds per test | JS bugs, Turbo issues, cross-page flow breaks |
Minitest over RSpec: a deliberate choice
Rails ships with Minitest. Use it. This is not a religious argument -- it is a practical one.
- Minitest is faster. Fewer layers of abstraction mean faster boot times and faster test execution. When you run tests hundreds of times a day, this adds up.
- Minitest is simpler. It is plain Ruby. No DSL to learn, no magic matchers, no implicit subject. New developers (and AI coding agents) can read and write Minitest tests immediately.
- Minitest is vanilla Rails. Every Rails guide, every stack overflow answer about Rails testing, every AI agent's training data -- all of it assumes Minitest. You are swimming with the current instead of against it.
- Minitest keeps tests grounded. Without RSpec's elaborate DSL, you are less tempted to over-abstract your tests. Tests stay readable, direct, and obvious.
RSpec is a fine tool. If your team already uses it and is productive with it, do not switch. But if you are starting a new SaaS in 2026, Minitest is the default for good reasons. Fight fewer tools, ship more product.
Testing patterns for Solid Queue jobs
Rails 8 ships with Solid Queue as the default job backend, replacing the need for Redis and Sidekiq for most SaaS applications. This changes how you think about testing background work.
Test the job, not the queue
The most common mistake with job testing is testing that a job was enqueued instead of testing what the job does. Enqueuing is Rails infrastructure -- it works. What your job does with the data is your business logic, and that is what breaks.
- Test the perform method directly. Call
YourJob.perform_now(args)in your test and assert on the side effects. Did the email send? Did the record update? Did the external API get called? - Test enqueuing only at boundaries. In a controller or model test, assert that the job was enqueued with the right arguments. Use
assert_enqueued_withfor this. - Test error handling. What happens when the job fails? Does it retry? Does it log? Does it update a status? These are the tests that save you at 2 AM.
Provisioning jobs: a real-world example
In a SaaS that provisions resources (repositories, environments, infrastructure), your provisioning job is critical. Here is what to test:
- The happy path: job receives valid input, provisions the resource, updates status to "live"
- Failure handling: external API fails, job updates status to "error", logs the failure, and does not leave orphaned resources
- Idempotency: running the job twice with the same input does not create duplicate resources
- Status transitions: the job correctly moves through pending, provisioning, live (or error) states
Notice what is not on that list: testing that Solid Queue persists the job to the database, testing retry mechanics, or testing that the queue processes jobs in order. That is framework behavior. Trust it.
System tests that do not make you miserable
System tests have a reputation for being slow, flaky, and painful to maintain. That reputation is earned -- but only when they are written badly. Here is how to write system tests that actually help.
Use headless Chrome, but have headed mode ready
Run headless in CI for speed. Run headed locally when debugging. Rails makes this easy with driven_by :selenium, using: :headless_chrome and environment-based switching.
One system test per critical journey
You do not need a system test for every page. You need one for each critical user journey:
- Sign up and complete onboarding
- Perform the core action that users pay for
- Upgrade or change subscription plan
- Invite a team member (if applicable)
Four to six system tests for an early-stage SaaS. That is it. Each one should test a complete flow, not a single page interaction.
Fight flakiness at the source
- Wait for elements, do not sleep. Use
assert_selectorandassert_textinstead ofsleep 2. Capybara has built-in waiting. - Isolate test data. Each test creates its own data and does not depend on data from other tests. Use fixtures or factory methods, not shared database state.
- Avoid testing time-dependent behavior. If a feature depends on the current time, use
travel_toto freeze time in your test. - Test user-visible outcomes. Assert on text the user sees, not on CSS classes or DOM structure. "I should see 'Project created'" is stable. "There should be a div with class project-card" breaks when you redesign.
Your CI pipeline: fast feedback or no feedback
A test suite is only useful if it runs. If your CI takes 20 minutes, developers stop waiting for it. If they stop waiting for it, they stop caring about it. The goal: CI under 5 minutes for a young SaaS.
The fast CI stack
- Linting first. Run RuboCop before tests. Lint failures are cheap to detect and should fail fast.
- Model and integration tests in parallel. These are fast and independent. Run them concurrently.
- System tests last. They are the slowest. Run them after everything else passes so you get fast feedback on quick failures.
- Security scanning. Bundle audit and Brakeman run in seconds. Include them in every CI run.
Local CI: the pre-push gate
Do not wait for GitHub Actions to tell you something is broken. Run your CI checks locally before pushing. A bin/ci script that runs linting, tests, and security scans is worth its weight in gold.
Pair this with git hooks (Lefthook, Overcommit, or plain git hooks) to run linting on pre-commit and the full suite on pre-push. You catch problems before they leave your machine, not after they hit the PR.
The best CI pipeline is the one that runs before you push. Local checks catch 90% of issues instantly. Remote CI is the safety net, not the first line of defense.
What not to test: the skip list
Knowing what to skip is as important as knowing what to test. Every unnecessary test is maintenance burden without business value.
-
Framework behavior. Do not test that
has_many :postsworks. Rails has its own test suite. Trust it. -
Trivial methods. A method that returns
first_name + " " + last_namedoes not need a test. You will see the bug in your UI before you see it in a test failure. - Third-party API internals. Stub external APIs at the HTTP boundary. Do not test that the Stripe gem serializes parameters correctly. Test that your code handles the response (or the failure) correctly.
- View rendering details. Do not assert on specific HTML tags, CSS classes, or element positions. These change constantly and break your tests without indicating real bugs.
- Admin interfaces. Early stage, your admin is you. One integration test to confirm the admin dashboard loads is enough. Full admin test coverage can wait until you have users who are not you.
- Marketing pages. Your landing page does not need a test suite. If the headline is wrong, you will see it. Ship the test budget on code that handles money.
When to add more tests
Your test suite should grow with your product, not ahead of it. Here are the triggers for expanding your test coverage:
- A bug reaches production. Every production bug gets a regression test. No exceptions. This is how your test suite learns from your mistakes and prevents repeats.
- You add a second developer. When you are solo, you hold the entire system in your head. The moment someone else touches the code, tests become communication. They tell your collaborator what the code is supposed to do.
- A feature gets complex. If you find yourself manually testing a feature because you are not confident in the edge cases, that is a signal. Write the test so you never have to manually check again.
- You are preparing to sell. Buyers and their engineers will review your test suite during technical due diligence. A codebase without tests gets a 20-30% haircut on valuation. Tests are literally worth money at exit.
- You are refactoring. Before you restructure code, write tests that lock the current behavior. Refactor with confidence. Delete the scaffolding tests after if they do not add ongoing value.
Fixtures vs factories: the Rails way
Rails ships with fixtures. Use them as your starting point. Here is why:
- Fixtures are fast. They are loaded once per test run via transactions, not created per test. This makes a real difference at scale.
- Fixtures are visible. Open
test/fixtures/users.ymland you see every user in your test suite. No hidden factory chains creating data you did not expect. - Fixtures enforce simplicity. If your fixtures file is getting complicated, your data model might be too complex. Fixtures act as a canary for design issues.
When fixtures become awkward -- deeply nested associations, many variations of the same model -- supplement with helper methods in your test files. You do not need FactoryBot. A simple method that creates a record with sensible defaults is often enough.
Testing with AI coding agents
In 2026, most Rails developers use AI coding agents (Claude Code, Cursor, Codex) for at least part of their workflow. This changes the testing equation in two important ways.
AI agents write tests faster than you
Generating test boilerplate is something AI agents excel at. Point an agent at a model or controller and ask it to write tests. It will produce a reasonable first draft in seconds. Your job shifts from writing tests to reviewing and refining them.
This means the "tests take too long to write" excuse is dead. If an AI agent can generate the test in 30 seconds, the only reason not to have it is that you did not ask.
Tests make AI agents more effective
Here is the counterintuitive part: a good test suite makes your AI coding agent better. When an agent modifies code and can immediately run the test suite to verify nothing broke, it catches its own mistakes. Without tests, the agent has no feedback loop and you become the manual tester for AI-generated code.
A codebase with tests is a codebase where AI agents can work autonomously. A codebase without tests is a codebase where every AI-generated change needs manual verification. The test suite pays for itself in agent productivity alone.
The testing checklist for launch
Before you launch your Rails SaaS, make sure you have at least these tests in place. This is the minimum viable test suite that lets you ship with confidence.
Model tests
- User validations (email format, password requirements, uniqueness)
- Subscription state transitions and access control logic
- Core domain model validations and business methods
- Any method that calculates, transforms, or makes a decision
Integration tests
- Every controller action returns correct status codes
- Authentication is enforced on protected routes
- CRUD operations persist and retrieve data correctly
- Webhook endpoints process events and return proper responses
- API endpoints (if any) return correct JSON structures
System tests
- Complete sign-up flow from landing page to dashboard
- Core user journey: the thing users pay you for, end to end
- Subscription upgrade/downgrade flow (if applicable at launch)
CI pipeline
- RuboCop linting passes
- All tests pass
- Bundle audit shows no known vulnerabilities
- Brakeman security scan passes
- Local
bin/ciruns the full suite in under 5 minutes
Ship with tests, not with fear
Testing is not a virtue. It is a tool. The founders who win are not the ones with the most tests or the highest coverage percentage. They are the ones who test the right things, skip the rest, and ship with confidence.
Your test suite should be an asset that accelerates development, not a liability that slows it down. If every test in your suite either catches a real bug or gives you confidence to deploy, you are doing it right. If you are maintaining tests that have not caught a bug in six months and test behavior that has not changed, delete them.
The best test suite is the one you actually run. Keep it fast, keep it focused, and keep it honest. Test what matters, skip what does not, and deploy every day.
Start with auth, billing, and your core happy path. Add tests when production teaches you where the gaps are. Let your CI pipeline enforce quality before code leaves your machine. That is the minimum viable test suite, and it is more than enough to ship a SaaS that works.
Ship with a test suite that works from day one
Omaship ships with a complete test suite, CI pipeline, Lefthook git hooks, and security scanning already configured. Start building features, not test infrastructure.
Want more guides?
Join the waitlist and we'll send new guides straight to your inbox.
No credit card. We send the free template links and 4 practical follow-up emails.