Mock-First Testing for AI Systems: Build Confidence Before Production
Learn how to test AI applications deterministically without calling expensive LLMs during every test run. We cover mock strategies, Pydantic contracts, evaluation gates, and production confidence.
The Problem: Testing AI Is Expensive
You build an AI feature. You want to test it. So you call OpenAI's API in your test suite.
Problem: Every test run costs money. OpenAI charges per token. If your test suite has 50 tests and each one calls GPT-4, you're spending dollars per test run. Your CI/CD pipeline becomes expensive. Developers stop running tests locally.
Worse: LLMs are non-deterministic. The same prompt returns different answers. Your test passes one time and fails the next. You can't trust your test results.
The solution is mock-first testing. Build your tests against mocks. Run them fast, free, and deterministically. Then, separately, run evaluation gates against real LLMs to catch regressions.
Three Layers of Testing
Think of AI testing as three layers stacked on top of each other.
Most developers skip straight to layer 3 and wonder why their CI is slow and expensive. Start with layer 1. Only call real LLMs when you have to.
Mock-First in Practice: SupportFlow Mini
SupportFlow Mini is a support ticket router. It takes a customer ticket, calls OpenAI to generate a recommendation, routes it to a human reviewer, and delivers the result to a ticketing system.
Here's how we test it without calling OpenAI in every test run.
Step 1: Define the Contract
First, we define what our AI module should output. We use Pydantic to enforce the shape and types.
This contract is the bridge between your mock and your real LLM. Both must return the same Pydantic shape. If they don't, your tests catch it.
Step 2: Build Mock Implementations
Create a mock AI provider that returns fixed, deterministic responses.
Both providers implement the same interface. Your tests accept a provider as a dependency. Inject the mock in tests, the real one in production.
Step 3: Write Deterministic Tests
Now your tests run against the mock. Fast, free, deterministic.
Every test passes or fails consistently. Run them 100 times in a row, same results every time. No flaky tests, no API costs.
Step 4: Evaluation Gates (Production Confidence)
Before shipping to production, you run a separate evaluation suite against real LLMs. This is intentional and measured, not every test run.
These tests run once before deployment. They're allowed to be slow and expensive because they only run when you're about to ship. They catch regressions. They give you confidence.
The Benefits
- ✓Fast feedback: Mock tests run in milliseconds. Developers get instant feedback.
- ✓Cheap CI: No API calls per test run. Your CI bill stays low.
- ✓Deterministic: No flaky tests. Same input always produces same output.
- ✓Production gates: Evaluation tests catch regressions before shipping.
- ✓Clear contracts: Pydantic forces consistent schemas between mock and real.
Common Pitfalls
Mock Diverges from Reality
Your mock returns "urgent" for every ticket with a keyword, but the real LLM is more nuanced. Fix: Make mocks smarter or accept that they're simplified. Run integration tests to catch the gap.
Evaluation Gates Are Too Loose
Your evaluation tests pass even though the model is degrading. Fix: Set thresholds based on production needs, not wishful thinking. If 92% accuracy matters, enforce it.
Skipping Evaluation Entirely
You ship when all mock tests pass. But the real LLM behaves differently. Fix: Always run holdout evaluation before shipping to production.
See It in Action
SupportFlow Mini demonstrates this pattern end-to-end. Check out the case study to see the full architecture, including how mocks feed into evaluation gates and production deployment.
SupportFlow Mini Case StudyKey Takeaways
- ✓Mock-first testing avoids expensive LLM calls in every test run while keeping tests deterministic.
- ✓Pydantic contracts ensure mocks and real implementations return the same shape.
- ✓Evaluation gates run separately before shipping, catching regressions against real LLMs.
- ✓Three layers: mock tests (fast, every commit), integration tests (staging), evaluation gates (pre-production).