Testing Quickstart
9 minute read
A practical guide to building a fast, reliable test suite that gives confidence without slowing down delivery. Focus on deterministic tests that run in CI and provide rapid feedback.
The Goal
Build a test suite that:
- ✅ Runs in under 10 minutes
- ✅ Is deterministic (same result every time)
- ✅ Catches real bugs before production
- ✅ Provides fast feedback to developers
- ✅ Doesn’t require heroic maintenance
Before You Begin
The Anti-Pattern to Avoid

Ice Cream Cone Anti-Pattern
Ice Cream Cone Testing = Lots of slow, fragile E2E tests, minimal fast unit/integration tests
Why this fails:
- E2E tests are slow (minutes per test)
- E2E tests are non-deterministic (flaky)
- Debugging E2E failures is time-consuming
- Developers stop trusting the tests
The Right Pattern
Most tests should be integration tests - fast, deterministic, testing real interactions without external dependencies.
See Test Patterns for the full testing matrix.
Week 1 Action Plan
Day 1: Audit Your Current Tests
Action: Categorize and time your existing tests
Create a test inventory:
Red flags:
- 🚩 Total CI time > 10 minutes
- 🚩 Flaky test rate > 1%
- 🚩 More E2E tests than integration tests
- 🚩 Tests that require deployed environments
Day 2: Fix or Delete Flaky Tests
Action: Zero tolerance for non-deterministic tests
Flaky tests destroy confidence. They must be fixed immediately or deleted.
Identify flaky tests:
Common causes of flakiness:
| Cause | Solution |
|---|---|
| Network calls to external services | Use test doubles |
| Database state from previous tests | Reset DB between tests or use transactions |
| Timing/race conditions | Use deterministic time, avoid sleep() |
| Shared mutable state | Isolate test data |
| Async operations without proper waiting | Use proper async test patterns |
Rule
If a test can’t be made deterministic in 1 hour, delete it. It’s better to have no test than a test you can’t trust.Day 3: Write Your First Integration Test
Integration tests are your highest-value tests. They test real component interactions without external dependencies.
Example: API Integration Test (Node.js)
Key characteristics:
- ✅ Deterministic - Same input = same output
- ✅ Fast - Runs in milliseconds
- ✅ Isolated - Uses test database
- ✅ Complete - Tests happy path and errors
- ✅ Real - Uses actual components, not mocks
See Integration Testing for patterns in other languages.
Day 4: Implement Test Doubles for External Services
Action: Mock external dependencies
External services (APIs, payment gateways, email, etc.) make tests slow and non-deterministic.
Example: Testing with External API
Test Double Types:
- Stub - Returns canned responses (use for queries)
- Mock - Verifies interactions (use sparingly)
- Fake - Working implementation (e.g., in-memory database)
See Test Doubles for detailed patterns.
Day 5: Reduce E2E Test Count
Action: Convert E2E tests to integration tests
E2E tests should only cover:
- Critical user paths (login, checkout, etc.)
- Scenarios that absolutely require a browser
- Integration between major system components
Before: E2E Test (Slow, Flaky)
After: Integration Test (Fast, Reliable)
Savings:
- 🚀 30 seconds → 50ms (600x faster)
- ✅ Deterministic (no browser timing issues)
- 🔧 Easier to debug (no UI layer)
Week 1 Results
After 5 days, you should have:
✅ Test inventory completed ✅ Zero flaky tests (fixed or deleted) ✅ 5-10 new integration tests covering critical paths ✅ External dependencies mocked using test doubles ✅ E2E tests reduced to < 5 critical scenarios ✅ CI time reduced (target: < 10 minutes)
Test-Driven Development (TDD)
Once you have a solid test foundation, consider TDD:
The Red-Green-Refactor Cycle:
Example TDD Flow:
See TDD Resources for learning materials.
Testing Matrix Reference
Use this matrix to determine where each test belongs:
| Test Type | Deterministic | Network | Database | Speed | % of Suite |
|---|---|---|---|---|---|
| Static | Yes | No | No | Instant | 100% |
| Unit | Yes | No | No | < 10ms | 20% |
| Integration | Yes | localhost | test DB | < 100ms | 70% |
| Contract | No* | Yes | - | < 1s | 5% |
| Functional | Yes | localhost | test DB | < 500ms | 4% |
| E2E | No | Yes | Yes | seconds | 1% |
*Contract tests run against live services but don’t break the build
Common Patterns by Language
JavaScript/TypeScript
- Framework: Jest, Vitest, or Mocha
- Integration: Supertest (HTTP), Testcontainers (DB)
- Mocking: Nock (HTTP), MSW (browser)
- E2E: Playwright, Cypress
Java
- Framework: JUnit 5, TestNG
- Integration: Spring Test, Testcontainers
- Mocking: Mockito, WireMock
- E2E: Selenium, RestAssured
Python
- Framework: pytest, unittest
- Integration: pytest-flask, pytest-django
- Mocking: pytest-mock, responses
- E2E: Selenium, Playwright
Go
- Framework: Built-in
testingpackage - Integration: httptest (stdlib), dockertest
- Mocking: gomock, testify/mock
- E2E: Selenium, chromedp
C# / .NET
- Framework: xUnit, NUnit, MSTest
- Integration: WebApplicationFactory, Testcontainers
- Mocking: Moq, NSubstitute
- E2E: Selenium, Playwright
Troubleshooting
“Our tests are still taking 15 minutes!”
Diagnose:
Common fixes:
- Run tests in parallel (
npm test -- --maxWorkers=4) - Use in-memory database instead of real DB
- Cache dependencies in CI
- Split test suite (fast tests in PR, full suite nightly)
“Tests pass locally but fail in CI”
Common causes:
- Timing differences (use deterministic time mocking)
- Environment differences (port conflicts, missing env vars)
- Test order dependency (tests should be independent)
- Race conditions (use proper async handling)
Fix:
“Developers skip tests because they’re too slow”
Reality: If tests slow down development, they’ll be skipped or removed.
Solutions:
- Speed up tests (see above)
- Run subset locally (fast tests only)
- Parallel execution
- Watch mode (only run changed tests)
Best Practices Summary
✅ DO:
- Write integration tests for most scenarios
- Make tests deterministic
- Keep CI under 10 minutes
- Test behavior, not implementation
- Use descriptive test names
- Fail fast (exit on first error)
❌ DON’T:
- Keep flaky tests
- Test private methods directly
- Use sleep/wait for arbitrary time
- Share test data between tests
- Mock everything (over-mocking)
- Write tests after the code (try TDD!)
See Testing Best Practices for comprehensive guidance.
Next Steps
After establishing your test foundation:
- Adopt TDD - Write tests first for new features
- Add contract tests - Verify API compatibility (Contract Testing)
- Implement mutation testing - Verify test quality
- Add performance tests - Catch regressions early
- Enable test coverage tracking - But don’t obsess over 100%
Further Reading
- Test Patterns Overview - Test pyramid, trophy, and ice cream cone
- Static Testing - Linting, type checking
- Unit Testing - Testing in isolation
- Integration Testing - Testing component interactions
- Contract Testing - API compatibility testing
- E2E Testing - Full system testing
- Test Doubles - Mocks, stubs, and fakes