Practical Apex testing tips: avoid seeAllData, use @testSetup, mock callouts, test bulk paths, assert failures, and leverage Test.startTest()/stopTest().
## Overview
Writing reliable Apex tests is essential for stable deployments and maintainable Salesforce code. This first post in our Tips & Tricks series focuses on practical techniques you can apply immediately to increase test coverage, speed up execution, and reduce flaky failures.
## Key Principles
### 1. Never rely on seeAllData=true
Using seeAllData=true couples tests to org state and makes them flaky. Create isolated test data using test factories or builder patterns so tests are deterministic and repeatable across sandboxes and CI.
### 2. Use @testSetup for shared data
Annotate a method with @testSetup to create common records once per test class. This reduces setup duplication and improves performance while keeping tests clear about their unique arrangements.
### 3. Mock external dependencies
For callouts use HttpCalloutMock or WebServiceMock and for Platform Events or Streaming consider event-driven stubs. Mocks keep tests fast and deterministic and let you assert how the code responds to different external outcomes.
### 4. Leverage Test.startTest() and Test.stopTest()
Wrap the behavior you want to profile or invoke asynchronous processing between Test.startTest() and Test.stopTest() to ensure governor limits reset and future/queueable/batch jobs execute within the test context.
### 5. Assert deliberately and comprehensively
Don't just assert code coverage. Verify important side effects: DML counts, field values, email or notification flags, and that exceptions are thrown for invalid inputs. Clear assertions reduce silent regressions.
### 6. Test bulk and edge cases
Write tests that exercise bulk processing with ranges of record counts (1, small bulk, near-governor limits). Also include negative tests and exception paths. Bulk tests catch issues that single-record tests miss.
### 7. Use factories and utility classes for clarity
Centralize record creation in test factory classes or builders. This keeps test logic focused on behavior rather than record plumbing, and lets you update fixtures in one place as schemas evolve.
## Quick checklist before committing
- No seeAllData=true unless absolutely required
- All callouts and async jobs mocked properly
- Tests exercise bulk, single, and negative flows
- Clear and specific assertions exist for outcomes
- Tests execute under CI within acceptable time budget
## Conclusion and next steps
Start by applying one or two of these tips to an existing fragile test class. Small, incremental improvements compound: fewer flaky failures, faster CI, and more confidence in deployments. In the next post we will walk through a real-world refactor example, converting a brittle test class into a robust, maintainable suite.
Interested in accelerating this process? Try Test Class Generator to auto-generate test classes and fixture factories you can customize and adopt into your CI pipeline.