If you’ve been following my recent posts, you already know I am a big fan of Trunk-Based Development (TBD), tiny pull requests, and deploying to production multiple times a day using feature flags. It sounds like an engineering dream: no long-lived branches, no merge hell on Friday afternoons, and features reaching users in hours instead of months.
But let’s be honest. When you tell a traditional QA manager or a risk-averse lead that developers are going to merge straight into the main branch and deploy to production ten times a day, they usually have a mini panic attack.
And they aren’t entirely wrong to worry.
If your team is merging code directly into the trunk every few hours, you cannot rely on manual regression testing. You cannot wait for a weekly QA cycle, and you certainly cannot spend two hours manually clicking through a staging environment to make sure a bugfix didn’t break the payment gateway.
To make Trunk-Based Development work without crashing production, you need an automated testing strategy built for speed and high confidence. You need a pipeline that gives you a definitive “Yes” or “No” in under 10 minutes.
Here is how we can build it from a backend perspective.
Why the Traditional Testing Pyramid Fails in TBD
We’ve all seen the classic testing pyramid: a massive base of unit test suites, a middle layer of integration tests, and a tiny peak of End-to-End (E2E) UI tests.
/\
/ \ <-- E2E / UI Tests (Slow, Flaky)
/____\
/ \ <-- Integration Tests (Medium Speed)
/________\
/ \ <-- Unit Tests (Fast, Isolated)
/____________\
In theory, it’s a great model. In a Trunk-Based Development workflow, however, teams often warp this pyramid into something that looks more like an ice cream cone. Frontend teams or QA engineers might want to cover every single user flow with massive E2E integration suites to feel safe.
But as backend developers, we hit a major bottleneck here: heavy end-to-end tests are notoriously slow.
If your CI pipeline takes 45 minutes to run because it needs to spin up frontend clients, log in dummy users, and click through a shopping cart just to verify a backend API change, your TBD flow breaks down entirely. Developers will stop merging frequently. They will accumulate code locally because waiting for the pipeline feels like a punishment. Suddenly, your small PRs become massive again, and you’re right back to square one.
To make TBD work, your pipeline must act as a fast feedback loop, not a bureaucratic checkpoint.
The Core Strategy: The 10-Minute Rule
The golden rule of Continuous Integration in a fast-paced team is simple: The commit-to-feedback loop must take less than 10 minutes.
If a developer pushes a change to the trunk (or opens a tiny, short-lived PR that will be merged within an hour), they should know almost immediately if they broke the build. If it takes longer than 10 minutes, the developer context-switches, starts a new task, and by the time the build fails, they have to drop what they are doing to fix a problem they’ve already forgotten about.
To achieve a 10-minute pipeline, you have to split your testing strategy into two distinct phases: Pre-Merge (Gatekeeping) and Post-Merge (Asynchronous Verification).
Phase 1: Pre-Merge Validation (The Gatekeeper)
Before code touches the main branch, your CI pipeline needs to run a highly optimized, strictly isolated suite of checks. This is your first line of defense.
1. Advanced Unit Testing & Mocking
Unit tests must form the absolute majority of your test suite. They should test pure logic, edge cases, and data transformations without touching external networks, file systems, or databases.
Instead of hitting a real database, use clean interfaces and mocks. For example, if you are testing a backend service that calculates a user’s premium subscription discount, do not query the database to fetch the user profile. Pass a mock object or a stub.
// Example of a fast, isolated unit test
describe('DiscountService', () => {
it('should apply a 20% discount for premium users', () => {
const mockUser = { id: 1, type: 'PREMIUM' };
const price = 100;
const finalPrice = DiscountService.calculate(mockUser, price);
expect(finalPrice).toBe(80); // Runs in milliseconds
});
});
JavaScriptA suite of 2,000 unit tests like this should run in under 60 seconds. If it takes longer, you are likely doing hidden I/O operations.
2. Contract Testing over Heavy Integration
When working with backend microservices, a common fear is that changing an API endpoint in Service A will break Service B. The instinctual response is to run massive integration tests that boot up multiple services at once.
Don’t do this in your pre-merge pipeline. Use Contract Testing instead (using tools like Pact).
Contract testing allows Service B (the consumer) to define a JSON file stating exactly what format it expects from Service A’s API. During Service A’s pipeline, it simply validates its code against this static contract file. It takes seconds, yet gives you the exact same confidence as a full integration test without spinning up a single extra server or relying on frontend E2E setups.
Phase 2: Post-Merge Validation (The Safety Net)
Once the code passes the 10-minute gatekeeper, it is merged into the trunk. Now, you can trigger the heavier, slower tests asynchronously. This runs in parallel or immediately after deployment to a staging environment.
1. Ephemeral Integration Testing
You do need to test real database interactions, but you should do it smart. Use short-lived, isolated environments. Tools like Docker and Testcontainers allow your pipeline to spin up a real PostgreSQL or Redis instance in a container, run database-dependent tests, and tear it down immediately.
Keep these tests focused purely on persistence logic—like verifying complex SQL queries or transaction rollbacks—rather than business logic.
2. Handling the “Flaky Test” Epidemic
Nothing destroys trust in an automated testing pipeline faster than a flaky test—a test that passes 9 times but fails on the 10th for no apparent reason (usually due to network timing, race conditions, or shared state in integration environments).
If a developer sees a pipeline fail, checks it, realizes it’s just “that one annoying flaky test again,” and hits Re-run, your testing culture is dying. Developers will start ignoring real failures.
You must enforce a strict Zero Tolerance Policy for Flakiness:
- Quarantine immediately: The moment a test shows flaky behavior, move it out of the main pipeline into a separate, non-blocking quarantine suite.
- Fix or delete: If a quarantined test isn’t fixed within a sprint, delete it. A non-existent test is better than a test that lies to your team.
Continuous Verification: Testing in Production
Even with a perfect pipeline, things will eventually go wrong. The ultimate evolution of a Trunk-Based Development testing strategy is recognizing that testing doesn’t stop when you deploy.
Since we use Feature Flags (as discussed in my last post), we can merge code to production completely hidden from users. This unlocks a powerful technique: Canary Deployments.
Instead of releasing a change to 100% of your traffic, route just 1% of real users to the new code. Your automated testing strategy here shifts from running test code to monitoring business metrics.
[Trunk Merge]
│
▼
[10-Min CI Pipeline Passes]
│
▼
[Deploy Hidden Behind Feature Flag]
│
▼
[Route 1% of Traffic (Canary)] ───► Monitor: HTTP 500s, Latency
│
├─── Error Spikes? ───► Automatic Rollback (Flag Off)
│
└─── Stable? ─────────► Gradual Rollout to 100%
If your APM tool (like Datadog or New Relic) detects a sudden spike in HTTP 500 errors or a drop in checkout conversions for that 1% segment, the feature flag automatically flips off.
Production monitoring is the final, most reliable tier of your testing pyramid.
Conclusion: Speed Breeds Quality
Moving to Trunk-Based Development requires a massive shift in how you think about quality assurance. It requires moving away from heavy, slow, protective gates toward fast, automated, and resilient feedback loops.
If you focus on keeping your unit tests pure, replacing heavy integration tests with contract testing, ruthlessly quarantining flaky tests, and using production monitoring as your final safety net, you can easily hit that 10-minute pipeline goal.
When your pipeline is that fast, developers catch bugs while the code is still fresh in their minds. It makes development predictable, safe, and incredibly fast.

