- al-testing-review.md: the generic ExpectedError cue's asserterror Assert.IsTrue/IsFalse exclusion was unconditional, but the specialized rule it deferred to only claims the pure-inversion shape. A test expecting the guarded Boolean-returning call itself to raise fell through both routes. Narrowed the exclusion to the same inversion-only condition the specialized cue already uses. - asserterror-needs-expectederror-and-code.md: the rollback-sentinel exception (a trailing asserterror Error(...) used purely to force a fixture rollback, not to verify a specific failure) previously lived only in skill routing prose. Encoded it directly in the article's Anti Pattern section so every consumer of the knowledge base sees it, not just this one skill. - commit-shared-test-fixture-inside-lazy-initialize.good.al/.bad.al: replaced hand-rolled Item.Init()/Insert(true) with LibraryInventory.CreateItem, so the canonical fixture doesn't itself trigger use-library-codeunits-for-test-fixtures. - table-relation-test-exclude-known-invalid-relations-via-event.good.al/ .bad.al: declared minimal "Sample Setup"/"Sample Header" tables inline instead of referencing undefined symbols, matching this repo's own convention that every fixture is self-contained.
2.8 KiB
| bc-version | domain | keywords | technologies | countries | application-area | |||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
testing |
|
|
|
|
Pin asserterror to a specific error with ExpectedError and ExpectedErrorCode
Description
asserterror passes when the guarded statement raises any error at all. That is too permissive for a negative test: a typo, a missing setup record, or a permission failure all raise errors, so a bare asserterror can go green while never exercising the rule it claims to verify — false confidence that the validation works. Constrain it. Assert.ExpectedError(text) checks the message of the error that was actually raised, and Assert.ExpectedErrorCode(code) checks its error code. Together they assert that the specific failure occurred, turning "something went wrong" into "the right thing went wrong for the right reason".
Best Practice
Follow every asserterror with a verification of the error it expects, and prefer the reusable Library Assert helpers over hardcoded literals. For a mandatory-field check, Assert.ExpectedTestFieldError(FieldCaption, ExpectedValue) encapsulates both the message and the TestField code, so the test survives caption or code changes and does not repeat that knowledge in every method. For other errors, pair Assert.ExpectedError with a stable substring — ideally a shared Label, not an inline sentence — and, where known, Assert.ExpectedErrorCode. When a needed check is missing from the shared library, extend Library Assert (or your own assert library) with a helper rather than hardcoding message text and codes across tests; matching on a code or an invariant fragment keeps the test from going blind to the wrong error when a caption is localized.
See sample: asserterror-needs-expectederror-and-code.good.al.
Anti Pattern
asserterror DoInvalid(); with nothing after it. The test asserts only that the call failed somehow; swap the validation for a different bug and the test still passes, certifying a guard that may no longer fire. A negative test that cannot tell one error from another verifies almost nothing.
Not an instance of this anti-pattern: a trailing asserterror Error(SomeLabel) used purely as an end-of-test rollback sentinel to undo a lazily-initialized shared fixture's scratch changes (see commit-shared-test-fixture-inside-lazy-initialize.md). That Error call exists to force a rollback, not to verify that a specific failure occurred — the sentinel's own text is not meant to be asserted against, and adding an ExpectedError there would just duplicate the label without checking anything the test doesn't already control.
See sample: asserterror-needs-expectederror-and-code.bad.al.