Salesforce RunRelevantTests vs LATdx

Two ways to stop running every Apex test - Last verified: September 24, 2026

Salesforce shipped a test level called RunRelevantTests in Spring ’26. It answers a question Apex teams have been asking for years: why does a four-line change have to run the whole suite? This page describes what the feature does, using Salesforce’s own documentation, then sets it beside what LATdx does, so you can tell which one fits your bottleneck. Every claim about Salesforce behaviour below links to a Salesforce source at the bottom of the page.

What RunRelevantTests is

RunRelevantTests is a deployment test level. It runs only the Apex tests relevant to the modified components in the deployment payload, so the number of tests that run scales with the size of the deployment rather than with the size of the org. It exists because of the shape of the alternatives: RunLocalTests, the default for Apex production deployments, runs every Apex test in the org except those from installed managed and unlocked packages, which in an org with a large suite means a long deployment for even a tiny change. RunSpecifiedTests is fast but puts the burden of naming the right tests on you.

It is a beta feature in Spring ’26, subject to the Salesforce Beta Services Terms, and is available in Lightning Experience and Salesforce Classic in the Enterprise, Performance, Unlimited, and Developer editions.

How you turn it on

Through the Salesforce CLI, set the test level on the deploy command:

sf project deploy start --test-level RunRelevantTests

Through a file-based call, set testLevel to RunRelevantTests on the DeployOptions object you pass to deploy(). The test level itself is available in all API versions; the two annotations below require API version 66.0 or later.

How it picks tests

The automatic analysis

The release note describes the mechanism as an engine that analyses the deployment payload and its dependencies and runs a subset of tests based on that analysis. Salesforce’s engineering blog is more specific about what that analysis is built from:

"RunRelevantTests relies on the compile-time dependency graph; it can’t detect dependencies introduced through dynamic dispatch or dependency injection patterns."

The analysis runs on the platform, against the components you are deploying. It is not derived from your git history and it does not look at your working tree.

The blind spot Salesforce documents

This is the part worth reading carefully, because Salesforce writes it down rather than leaving you to discover it. The worked example in their post is a class that resolves an implementation at runtime:

public class Caller {
  public void makeCall(String delegateImplementationName) {
    Type delegateImplementation =
      Type.forName(delegateImplementationName);
    Delegate delegate = (Delegate) delegateImplementation.newInstance();
    delegate.doSomething();
  }
}

Salesforce’s own description of what happens next:

"The dependency structure above creates a blind spot: while Caller depends on Delegate at compile time, it has no compile-time link to DelegateImpl. The dependency graph cannot track the runtime resolution of DelegateImpl into Caller.makeCall(). RunRelevantTests may omit a test required to validate the change."

That is a documented, first-party statement that the selection can omit a test the change needed. It is not a criticism: it is the honest limit of any compile-time graph, and the same wall LATdx hits, which we cover below.

The two annotations that close the gap

Salesforce’s prescribed fix is to declare the dependency yourself, with one of two @IsTest parameters:

  • @IsTest(testFor='ApexClass:ClassName, ApexTrigger:TriggerName') runs the test class whenever the named classes or triggers are new or changed in the payload. In the example above, @IsTest(testFor="ApexClass:DelegateImpl") is what pulls CallerTest back into the run.
  • @IsTest(critical=true) runs the test class whenever any Apex changes, regardless of the payload. Salesforce advises using it sparingly: "Using it where it’s not needed reduces the time savings afforded by RunRelevantTests."

Both require API version 66.0 or later. The practical cost is that your dependency-injection seams, the ones you introduced to make code testable, become annotations you have to keep in sync by hand. Miss one, and the selection silently narrows.

What Salesforce does not publish

The input is documented (a compile-time dependency graph); the traversal is not. Nothing we could find states how many levels of that graph the engine walks, and the difference between "tests that directly reference the changed class" and "tests that reach it through any number of intermediate classes" is the difference between a safe selection and a missed regression in a layered codebase. We are not going to assert a depth Salesforce has not stated.

Where it fits: deployments, not ad-hoc runs

This is the single most useful thing to know before you compare anything else. RunRelevantTests is a deployment test level. The Salesforce CLI command for running tests without deploying, sf apex run test, documents three test levels: RunLocalTests, RunAllTestsInOrg, and RunSpecifiedTests. Relevance-based selection is not one of them.

So if your pain is "the deploy takes 40 minutes because it runs every test," this feature is aimed squarely at you. If your pain is "a developer changes one class and waits on the suite before they have deployed anything," it does not reach that loop.

How LATdx approaches the same problem

LATdx works from your repository rather than from a deployment payload. It parses your Apex into a dependency graph and, for each test method, computes the transitive closure of everything that method can reach: the classes it calls, the classes those call, and the objects and fields they touch. latdx test run --affected intersects that closure with the Apex and metadata files in a git delta, so the selection is available in a pull request before anything is deployed.

The second mechanism is result caching. Each test method’s last outcome is stored against a hash of its own source, the source of every class in its closure, and an org-level schema digest. When none of those inputs have moved, the stored pass or fail is replayed instead of re-executed. The rule is that a cached PASS has to be worth exactly as much as a fresh one, so any uncertainty falls through to a real run on the org, and every decision is explainable with latdx test cache explain. Tests still execute on your real Salesforce org, through the CLI connection you already have; nothing is simulated locally.

What LATdx cannot see either

Static analysis has hard edges, and they are not that different from Salesforce’s:

  • Dynamic Apex: the same wall Salesforce documents. An AST cannot follow Type.forName("Foo").newInstance(), Database.query on a built string, or reflection through Callable. If a test exercises dynamic dispatch and the code behind it changes, run with --no-cache or clear the cache for that class.
  • Apex triggers: a trigger fires on DML, so no static call edge runs from a test method to it. A trigger change is flagged and the affected list is reported as incomplete.
  • Flows, custom labels, custom metadata, validation rules, permission sets, profiles, record types, sharing rules, and workflows: these are surfaced as known false-negative kinds, with a warning to run the full suite.
  • @IsTest(SeeAllData=true) tests read live org data that can change with no source edit, so they are never served from the cache and always run fresh.

The design choice is to fail loudly rather than quietly: an unprovable change widens the selection or raises a warning, it never silently narrows it. Full detail is in the test caching documentation.

Side by side

DimensionSalesforce RunRelevantTestsLATdx
Where it runsA deployment test level: the --test-level flag on sf project deploy start, or testLevel on the Metadata API DeployOptions object.A CLI you install: latdx test run --affected locally or in CI, and latdx validate when you also want the deploy.
What triggers the selectionThe components present in the deployment payload.A git delta between two refs, or an explicit file, class, or test-method list.
How the set is derivedThe compile-time dependency graph of the deployment payload, computed server side.An AST-derived reach closure built from your repository on your machine, walked transitively.
Documented analysis depthThe input is documented (the compile-time dependency graph); the traversal depth is not published.The full transitive set of classes a test method can reach, plus the objects and fields it touches.
Dynamic dispatch and dependency injectionDocumented blind spot: Salesforce states the graph cannot track runtime resolution, and that a required test may be omitted. You close it by hand with annotations.Same blind spot, documented in our own limitations: an AST cannot follow Type.forName or dynamic SOQL either. Mitigate with --no-cache or a cache clear while iterating on dynamically dispatched code.
Escape hatch when selection misses a testAnnotate the test class: @IsTest(testFor='ApexClass:Name, ApexTrigger:Name') for a named dependency, @IsTest(critical=true) to run on any Apex change (Salesforce advises using it sparingly).Over-include rather than guess: when reach cannot be proven statically the test is kept in the set, and unprovable metadata kinds raise a warning telling you to run the full suite.
Reuse of results between runsNot a documented capability. The selected tests execute on every deployment.A per-test-method result cache keyed on the test source, its transitive reach, and an org schema digest. An unchanged test replays its last outcome instead of executing.
Ad-hoc test runs without a deploymentNot available. sf apex run test accepts RunLocalTests, RunAllTestsInOrg, and RunSpecifiedTests.The primary use: selection and caching apply to any run, deploy or not.
Release statusBeta in Spring '26, subject to the Salesforce Beta Services Terms. Salesforce states it should not yet be integrated into production pipelines.Shipped, installed from the public release channel.
AvailabilityLightning Experience and Salesforce Classic in Enterprise, Performance, Unlimited, and Developer editions.Any org your sf CLI is already authenticated to.
CostIncluded in the platform.Free tier, then paid plans.

When the built-in feature is enough

Plainly: for a lot of teams it is, and you should not pay for a tool you do not need. Use RunRelevantTests on its own when:

  • Your slow step is the deployment itself, and developers are not waiting on tests anywhere else.
  • You deploy through sf project deploy start or the Metadata API, so the test level is a one-flag change with nothing new in the pipeline.
  • You want to trim the intermediate stages of a pipeline (integration, UAT, staging) and keep a full run for the production release, which is exactly the shape Salesforce describes.
  • Your suite is small enough that a full RunLocalTests pass is an inconvenience rather than a blocker.
  • You are willing to maintain testFor and critical annotations by hand wherever dynamic dispatch or dependency injection hides a dependency from the compile-time graph.

It is one flag, it costs nothing, and it is maintained by the people who own the platform. That is a strong position, and Salesforce sets the expectation honestly: "For a production release, running every test is essential. A class that passes its own tests in isolation can still break a process three layers away." The things left to weigh are its beta status, the annotation maintenance, and the fact that the selection logic is not something you can inspect from outside.

When a fuller closure plus caching helps

  • Developers run Apex tests before they deploy anything. RunRelevantTests is a deployment test level, so the inner loop is out of its scope.
  • The same unchanged subsystem is retested on every CI run, and you want the second run to be cheaper than the first.
  • Dependencies are several classes deep and you would rather not curate testFor annotations to cover the indirect ones.
  • You need the selection to be auditable: which classes a test reaches, what its fingerprint is, and which rule authorised a skip.
  • Your pipeline selects from a pull request's git diff, before there is a deployment payload to analyse.

The two mechanisms compound. Selection removes the tests a change cannot reach; the cache removes the tests it can reach but has not changed since their last run. A deployment test level can do the first for a deployment. Neither the platform nor a payload-shaped analysis can do the second, because reusing a result requires knowing that nothing the test depends on has moved, which is a property of your source history rather than of the payload in front of it.

Questions teams ask

Is the RunRelevantTests analysis really only one level deep?

Salesforce has not published the depth, and you should be careful with anyone who tells you otherwise. What Salesforce does publish is the input: a compile-time dependency graph. How far the engine walks that graph is not stated in the release note or the engineering post. Hands-on write-ups by community members report direct references being picked up and dynamically instantiated classes being missed, which matches the documented blind spot but does not settle the traversal depth. Treat "one hop" as an observation about a beta, not a documented contract, and measure it against your own org.

Does RunRelevantTests change the code coverage requirement?

Neither the release note nor the engineering post addresses coverage, so we are not going to answer it for Salesforce. What both do say is that the feature is beta and, in Salesforce’s words, "should not yet be integrated into production pipelines." Since coverage is measured from the tests that actually executed, running fewer of them is exactly the condition under which a coverage gate can bite. Validate a representative change in a sandbox before you put the flag anywhere near production.

Can I use both?

Yes, and they do not overlap much. RunRelevantTests trims the tests a deployment runs; LATdx trims and caches the tests a developer or a CI job runs. You can also feed one into the other: latdx test run --affected --dry-run prints the selection as plain Class.method lines, which are valid input to sf apex run test --tests or a RunSpecifiedTests deployment.

What happens when neither tool can prove what a change affects?

They diverge on the default. Salesforce asks you to state the dependency yourself, in an annotation, and says the run may otherwise omit a test the change needed. LATdx over-includes instead: where reach cannot be proven it keeps the test in the set and reruns it, and it refuses to serve a cached result it cannot justify. Neither approach conjures information that is not in the source. The difference is which way the uncertainty falls when nobody has written an annotation.

Try it on your own suite

The only number that matters is the one from your codebase. How much a selection tool helps depends entirely on the shape of your dependency graph: a wide codebase of loosely coupled services selects down to almost nothing, and a codebase where one utility class is touched every sprint selects down to almost everything. Run it and look.

Install the CLI and run the quickstart, read the documentation, or see what a license costs on the pricing page. What LATdx can and cannot see, and what never leaves your machine, is written out on the security page.

Sources

Every statement about Salesforce behaviour on this page comes from one of these. Last verified: September 24, 2026. Salesforce beta features change; check the release notes for your org before relying on any of it.

LATdx is not affiliated with, endorsed by, or sponsored by Salesforce. Salesforce, Apex, and related marks are trademarks of Salesforce, Inc. Found something on this page that is out of date or wrong? Tell us and we will correct it.