System.AssertException: Assertion Failed: Expected: <X>, Actual: <Y>
An Apex test ran an assertion that didn't match. The Expected vs Actual values are in the message — that's where you start. The fix is either to make the code match the test's expectation, or to update the test if the new behaviour is intentional.
Also seen asSystem.AssertException·Assertion Failed: Expected·AssertException: Assertion Failed·test failure assertEquals
Apex tests use assertions to verify behaviour. A failed assertion throws AssertException, prints the expected vs actual, and fails the test. The platform reports the failure during deploy or during local test runs.
How to read the message
System.AssertException: Assertion Failed:
Expected: 100, Actual: 0:
Expected balance to update after deposit
Three parts:
- Expected: 100 — what the test wanted
- Actual: 0 — what it got
- The optional message — what the test was checking conceptually
If your test omits the optional message, you only see Expected/Actual without context. Always include a message — Expected: 100, Actual: 0 is hard to read in a failure list, but Expected: 100, Actual: 0: Expected balance to update after deposit tells you immediately what's broken.
The two debugging paths
1. The test's expectation is correct; the code is broken
The most common case during deploy. Your code change inadvertently broke logic the test was guarding. Open the test, read what it does, then trace the code path to find where the change broke the contract.
2. The test's expectation is now stale
You changed the behaviour intentionally — the test needs to be updated. Common during refactors:
@isTest static void depositIncreasesBalance() {
Account a = new Account(Name = 'Test');
insert a;
Bank.deposit(a.Id, 100);
a = [SELECT Balance__c FROM Account WHERE Id = :a.Id];
System.assertEquals(100, a.Balance__c, 'Balance should increase by deposit amount');
}
If you renamed Balance__c to Account_Balance__c, the test's SELECT fails or returns null. Update the test to match the new field name.
A common smell: test passes but isn't checking anything
@isTest static void depositWorks() {
Bank.deposit('001xxx', 100);
System.assert(true); // 🚨 always passes
}
This is a coverage cheat — runs the code without verifying behaviour. The platform counts the lines as covered, but the test is meaningless. Search your codebase for System.assert(true) and System.assertEquals(1, 1); replace with real assertions.
A common cause of mysterious test failures
Test data dependency on org config. If your test relies on a specific record-type, picklist value, or hierarchy that exists only in your sandbox, it'll fail in a fresh org or scratch org. Three ways to handle:
- Build the test data in the test itself with
@TestSetup. Don't rely on org-level data. - Use
Test.loadData()to load CSVs committed in static resources. - Mock the dependencies with abstract interfaces and stub implementations.
The third is best — your test class becomes self-contained, runs anywhere, and exercises pure logic.
When you see assertion failures in unrelated tests
Sometimes a deploy breaks tests in classes you didn't touch. Cause: a shared @TestSetup method now produces different data because of a schema change. Trace it back to the setup method; the problem is one layer up from where the test fails.
Run failing tests locally before deploying
Setup → Apex Test Execution. Run only the failing tests. Iterate locally with Test.startTest() / Test.stopTest() patterns to isolate the failing assertion. Then deploy.
