apex-test-setup-patterns

@TestSetup method semantics: one-time creation per test class, isolation behavior, @TestVisible, System.runAs, Test.startTest/stopTest governor reset, mixed-DML boundaries. NOT for building a test data factory (use test-data-factory-patterns). NOT for mocking callouts (use apex-http-callout-mocking).

Best use case

apex-test-setup-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

@TestSetup method semantics: one-time creation per test class, isolation behavior, @TestVisible, System.runAs, Test.startTest/stopTest governor reset, mixed-DML boundaries. NOT for building a test data factory (use test-data-factory-patterns). NOT for mocking callouts (use apex-http-callout-mocking).

Teams using apex-test-setup-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/apex-test-setup-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/PranavNagrecha/AwesomeSalesforceSkills/main/skills/apex/apex-test-setup-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/apex-test-setup-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How apex-test-setup-patterns Compares

Feature / Agentapex-test-setup-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

@TestSetup method semantics: one-time creation per test class, isolation behavior, @TestVisible, System.runAs, Test.startTest/stopTest governor reset, mixed-DML boundaries. NOT for building a test data factory (use test-data-factory-patterns). NOT for mocking callouts (use apex-http-callout-mocking).

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Apex Test Setup Patterns

Activate when writing or reviewing an Apex test class. `@TestSetup` controls one-time data creation shared across every test method, `Test.startTest()`/`Test.stopTest()` define the governor-reset boundary, and `System.runAs` defines which user the test impersonates. Getting any of these wrong produces tests that pass flakily, misreport coverage, or exercise the wrong user context.

## Before Starting

- **Decide what goes in setup vs per-test.** Setup data is rolled back after the class finishes, not after each method — but each method sees a fresh rollback-to-setup snapshot.
- **Plan the runAs scope.** Setup runs as the test-class user unless wrapped in `System.runAs`.
- **Identify governor-reset needs.** Any async/bulk work inside `Test.startTest()` gets its own 100-callout / 100-SOQL / etc. budget.

## Core Concepts

### @TestSetup

```
@IsTest
private class AccountServiceTest {
    @TestSetup
    static void setup() {
        Account[] accs = new List<Account>{
            new Account(Name = 'A1'),
            new Account(Name = 'A2')
        };
        insert accs;
    }

    @IsTest static void testFoo() {
        Account a = [SELECT Id FROM Account WHERE Name = 'A1'];
        // ...
    }
}
```

Runs once per test class before any test method. Each test method starts with setup-state data; changes made inside a test method are rolled back after that method completes.

### Test.startTest() / Test.stopTest()

```
Test.startTest();
// Code inside gets a fresh set of governor limits.
// Any async jobs enqueued here (Queueable, future, Batch) run synchronously at stopTest().
Test.stopTest();
```

Critical for two reasons:
1. **Governor reset** — isolates setup work from the code-under-test's limit budget.
2. **Async flush** — future/Queueable/Batch jobs registered before `stopTest` execute synchronously when `stopTest` is called.

### @TestVisible

Annotation on a `private` member that makes it visible to test classes (but NOT to production code). Use when tests need to inject state or call a helper that shouldn't be `public`.

```
public class OrderService {
    @TestVisible private static Integer retryCount = 3;
    @TestVisible private static Boolean simulateFailure = false;
}
```

### System.runAs

```
User u = [SELECT Id FROM User WHERE Profile.Name = 'Standard User' LIMIT 1];
System.runAs(u) {
    // DML as that user; CRUD/FLS/Sharing enforced per their profile
}
```

Also the only way around **mixed DML** — setup-object DML (User, UserRole, Group) cannot coexist with non-setup DML in a test unless isolated via `System.runAs(new User(Id = UserInfo.getUserId()))`.

### Mixed DML workaround

```
System.runAs(new User(Id = UserInfo.getUserId())) {
    insert new User(...);  // setup-object DML
}
insert new Account(...);    // non-setup DML — now legal
```

## Common Patterns

### Pattern: Setup with runAs for ownership

```
@TestSetup
static void setup() {
    User u = TestUserFactory.createStandardUser();
    insert u;
    System.runAs(u) {
        insert new Account(Name = 'Owned by u');
    }
}
```

### Pattern: startTest for async flush

```
@IsTest static void testQueueable() {
    Test.startTest();
    System.enqueueJob(new MyQueueable());
    Test.stopTest();  // Queueable runs synchronously here
    System.assertEquals(1, [SELECT COUNT() FROM Task]);
}
```

### Pattern: @TestVisible injection for failure simulation

```
@IsTest static void testRetryOnFailure() {
    OrderService.simulateFailure = true;  // @TestVisible flag
    // assert retries
}
```

## Decision Guidance

| Situation | Approach |
|---|---|
| Multiple tests share identical data | `@TestSetup` |
| Each test needs unique/customized data | Per-method inline creation |
| Exercising async (future/Queueable/Batch) | Wrap in `Test.startTest/stopTest` |
| Need to test a different user's perspective | `System.runAs(u)` |
| Mixed setup + non-setup DML | `System.runAs` guard around setup-object DML |
| Override a private internal flag | `@TestVisible` |

## Recommended Workflow

1. Identify data common to every test method → move to `@TestSetup`.
2. Create users in `@TestSetup` via a `TestUserFactory` inside `System.runAs(new User(Id = UserInfo.getUserId()))` to avoid mixed DML.
3. Per-method: wrap code-under-test in `Test.startTest()` / `Test.stopTest()`.
4. For async, enqueue before `stopTest`; assert results after.
5. For user-perspective tests, use `System.runAs(u) { ... }` inside the method.
6. Use `@TestVisible` sparingly — prefer dependency injection via method params.
7. Never use `SeeAllData=true` on new tests.

## Review Checklist

- [ ] `@TestSetup` used for shared data (not repeated per method)
- [ ] Setup-object DML (User, Role, Group) isolated via `System.runAs`
- [ ] `Test.startTest/stopTest` wraps code-under-test
- [ ] Async jobs flushed at `stopTest`
- [ ] No `SeeAllData=true`
- [ ] `@TestVisible` used only where DI isn't feasible
- [ ] Setup method itself does not depend on org data

## Salesforce-Specific Gotchas

1. **If `@TestSetup` throws, all test methods in the class fail.** Keep setup focused; move optional data to per-method builders.
2. **`Test.stopTest()` resets limits only once per test method.** You cannot nest start/stop pairs.
3. **`@TestSetup` runs once — not once per method.** Static variables set inside setup persist across methods.
4. **Mixed DML rule doesn't apply in `@TestSetup`** when `System.runAs` isn't present — the initial setup user context allows it. But if you enter a `runAs` block you're back to mixed-DML constraints.

## Output Artifacts

| Artifact | Description |
|---|---|
| Test class with `@TestSetup` | Shared data block |
| runAs wrapper patterns | User-context isolation + mixed-DML guards |
| `Test.startTest/stopTest` placement | Governor + async flush discipline |

## Related Skills

- `apex/test-data-factory-patterns` — shared data factory design
- `apex/apex-http-callout-mocking` — mocking HTTP in tests
- `apex/apex-system-runas` — user-context testing details

Related Skills

shield-kms-byok-setup

8
from PranavNagrecha/AwesomeSalesforceSkills

Configure Shield Platform Encryption with customer-supplied (BYOK) or customer-held (Cache-Only Key Service) tenant secrets, rotate them, and recover. NOT for Classic Encryption or field masking.

mfa-enforcement-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design MFA enforcement: auto-enablement, Salesforce Authenticator rollout, exceptions, service accounts, API-only users, SSO interop, and audit. Trigger keywords: MFA, multi-factor, two-factor, Salesforce Authenticator, MFA exception, MFA SSO, api-only MFA. Does NOT cover: end-user password policies, device-trust posture, or non-Salesforce IdP configuration.

encrypted-field-query-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Design SOQL, filters, reporting, and indexes against Shield Platform Encryption fields. Trigger keywords: Shield Platform Encryption, encrypted field query, probabilistic vs deterministic encryption, encrypted SOQL filter, encrypted field index. Does NOT cover: Classic Encryption (deprecated), field-level security policy, or tenant secret key rotation.

apex-managed-sharing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Grant row-level access programmatically via __Share records when declarative sharing rules cannot express the policy. NOT for OWD, role hierarchy, or criteria-based sharing rule design.

omnistudio-testing-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when testing or validating OmniStudio components — OmniScript preview, Integration Procedure step debugging, DataRaptor field-mapping validation, and end-to-end UTAM-based automation. NOT for Apex unit testing or standard Flow debugging.

omnistudio-error-handling-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing fault behavior across Integration Procedures, DataRaptors, OmniScripts, and FlexCards — error routing, user-facing messaging, retry semantics, and idempotency. Triggers: 'omnistudio error', 'integration procedure fault', 'dataraptor error handling', 'omniscript retry', 'flexcard action failure'. NOT for general Apex exception design or Flow fault paths.

omnistudio-ci-cd-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or implementing CI/CD pipelines for OmniStudio components — DataPack export/import, versioning, environment promotion, and automated deployment. NOT for standard Salesforce metadata CI/CD or Apex-only pipelines.

omniscript-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniScripts for guided experiences, step structure, branching, save/resume, and the boundary between OmniScript, Integration Procedures, DataRaptors, and custom LWCs. Triggers: 'omniscript design', 'too many steps in omniscript', 'save and resume omniscript', 'branching in omniscript', 'when should this be an integration procedure'. NOT for deep Integration Procedure or DataRaptor design when the guided interaction layer is not the main concern.

integration-procedure-cacheable-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing Integration Procedures (IPs) with platform cache to cut latency and callout load. Covers cache key design, TTL selection, per-user vs org-wide partitions, invalidation on data changes, and safe fallback on cache miss/stale. Does NOT cover general IP authoring (see omnistudio-error-handling-patterns) or LWC client-side caching.

flexcard-design-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing, building, or reviewing OmniStudio FlexCards — including data source selection, card states, actions, conditional visibility, flyout configuration, and child card iteration. Triggers: 'FlexCard', 'card template', 'flyout', 'card action', 'card state', 'data source', 'child card', 'conditional visibility'. NOT for OmniScript design, standalone LWC development, or Apex controller architecture outside the FlexCard context.

dataraptor-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing OmniStudio DataRaptors, especially Extract versus Turbo Extract versus Transform versus Load, field mapping strategy, performance tradeoffs, and when to move work into Integration Procedures or Apex. Triggers: 'DataRaptor Extract', 'Turbo Extract', 'DataRaptor Load', 'DataRaptor Transform', 'OmniStudio data mapping'. NOT for overall OmniScript journey design or Integration Procedure sequencing when the main question is not the DataRaptor shape itself.

wire-service-patterns

8
from PranavNagrecha/AwesomeSalesforceSkills

Use when designing or reviewing Lightning Web Components that use `@wire`, Lightning Data Service, UI API, or the GraphQL wire adapter, especially for reactive parameters, cache behavior, and refresh strategy. Triggers: 'wire service', 'refreshApex', 'reactive parameter', 'getRecord', 'wire vs imperative Apex'. NOT for component communication or generic lifecycle issues when data provisioning is not the main concern.