Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

Straight answer: the assertion failed and nobody told it what to say. System.AssertException: Assertion Failed is the default message, and it appears because the assertion was written without its optional message argument. The fix is to always pass one, and to make it say what was expected rather than that something was expected.

System.AssertException: Assertion Failed

That is the entire output. A test run of four hundred methods gives you a class name, a method name and those two words. You now have to read the test to find out what it was checking.

Every assertion takes a message

Both the old System methods and the Assert class accept a trailing message argument, and in both cases it is optional. Optional is doing a lot of damage here.

// no help at all
System.assert(results.isEmpty());
Assert.areEqual(3, rows.size());

// tells you what broke without opening the file
Assert.isTrue(results.isEmpty(),
    'Expected no errors for a valid payload, got: ' + results);
Assert.areEqual(3, rows.size(),
    'Expected 3 line items after the split, got ' + rows);

The equality assertions are slightly better off than the boolean one, because a failure reports the expected and actual values. But it reports them without context: “Expected: 3, Actual: 2” tells you a number was wrong, not which number.

The Assert class is the current form

Apex has a dedicated Assert class with named methods, and it reads better than the System equivalents:

  • Assert.areEqual(expected, actual, msg) and Assert.areNotEqual
  • Assert.isTrue, Assert.isFalse
  • Assert.isNull, Assert.isNotNull
  • Assert.isInstanceOfType and its negative, for checking exception types
  • Assert.fail(msg), for the line after a call that was supposed to throw

Assert.isNull(x, 'msg') makes the intent obvious where System.assertEquals(null, x) requires a moment’s thought about argument order. The older methods still work and are not going anywhere. New tests should use the newer class.

Argument order matters in the equality methods: expected first, actual second. Reversing them does not change whether the test passes, only whether the failure message is a lie. That is worse than no message.

What a good assertion message contains

Three things, and the third is the one people leave out.

  1. What was being checked, in domain terms. Not “size should be 3” but “expected 3 line items after splitting the order”.
  2. Why that is the right answer, when it is not self-evident. “Discount applies only above 100 units” saves the next reader a trip to the requirements.
  3. The actual value, concatenated in. A test failing in a scheduled run on a Sunday is often all you get, and the value in the message is the difference between diagnosing it and reproducing it.

Dumping a whole record into the message is fine. The message costs nothing when the test passes.

The assertion that never runs

A related and quieter problem: a test method with a try/catch that swallows the failure.

// passes whatever happens
try {
    service.process(records);
    Assert.fail('Expected a validation error');
} catch (Exception e) {
    // silently accepts any exception, including a NullPointerException
}

Catch the specific type you expect, and assert on the message:

try {
    service.process(records);
    Assert.fail('Expected DmlException for a missing required field');
} catch (DmlException e) {
    Assert.isTrue(e.getMessage().contains('REQUIRED_FIELD_MISSING'),
        'Wrong DML error: ' + e.getMessage());
}

Without the Assert.fail line, a method that stops throwing passes the test forever.

One more thing about managed packages

Assertion statements in managed package code are not executed when that code runs in a subscriber org outside of package test execution. If you are building on a package and expecting its internal assertions to protect you, they are not running. Your own assertions in your own org are unaffected.

A test whose assertion says nothing is the same problem as a test with no assertion, which is the subject of coverage without useless tests.

Frequently asked

Which line failed? The stack trace names it. If the assertion sits in a shared helper called from twenty tests, the trace shows the helper, which is exactly when a distinguishing message earns its keep.

Should one test have several assertions? Yes, as long as they check one behaviour. The argument against multiple assertions is really an argument against tests that check several unrelated things, and a message on each one removes most of the objection anyway.

Is System.assert deprecated? Not deprecated, just superseded by clearer methods. There is no need to rewrite passing tests. Write new ones with Assert.

Can I assert on a governor limit? Yes, and it is underused. Asserting that a bulk operation used one query rather than a hundred catches a regression that no functional assertion will notice.