bcquality/microsoft/knowledge/testing/test-one-when-per-test.md
Michael Dieringer 0120b874b2 Address Jesper Schulz-Wedde's review on PR #157
- release-must-update-app-version.md: reframe around AppSource's actual
  strict full-version-ordering requirement; scope branching-policy
  claims as team convention, not platform rule.
- pictures-must-use-media-not-blob.md: MediaSet is a collection of
  independent media objects, not automatic image variants/thumbnails.
- log-writes-must-survive-rollback.{md,good.al}: StartSession's only
  data channel into the new session is its Record parameter to a
  TableNo-scoped codeunit; a setter called on a local instance before
  starting the session populates nothing in the new session.
- exposed-objects-must-be-in-a-permission-set.md: correct the three
  exposure mechanisms (Web Services config, PageType/QueryType=API,
  ServiceEnabled as a method-only attribute).
- pages-must-not-contain-business-logic.md: scope to persisted
  mutations and cross-entry-point rules; presentation-only
  calculations and table-owned invariants are not violations.
- given-blocks-must-cover-full-precondition-chain.good.al: replace
  invented LibrarySales calls with the real API
  (CreateCustomer/CreateSalesOrderForCustomerNo/PostSalesDocument).
- test-feature-scenario-tags.{md,good.al}: move [SCENARIO] inside the
  test procedure body to match the current BCApps corpus; keep
  [FEATURE] at codeunit level per Microsoft's own documented option.
- ui-test-codeunit-naming.md: scope the _UT suffix and adjacent-ID
  pairing as an explicit team convention, not a BCApps-wide standard.
- page-design-must-match-bc-page-type-conventions.md /
  table-design-must-match-bc-table-type-conventions.md: Card's
  single-key primary-key claim is a contextual heuristic, not a
  mandatory constraint (Ship-to Address, Customer/Vendor Bank Account
  are real composite-key Card pages); a Subsidiary table with its own
  identity commonly gets List+Card, not Worksheet/Tabular.
- api-page-least-privilege-write-access.{md,good.al}: only page-placed
  fields are ever exposed; set InsertAllowed/DeleteAllowed=false in the
  good sample so a narrow field set can't still create/delete records.
- source-organized-by-feature-not-object-type.md,
  test-one-when-per-test.md: scope as team/testing-design conventions,
  not Microsoft platform requirements.
- upgrade-tag-logic-must-not-nest-deeply.md: add the Microsoft Learn
  citation that already backs the two-level nesting limit.
- Wire the new articles into the testing/data-modeling/error-handling/
  security/ui review skills' candidate-selection signals.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-21 22:43:16 +02:00

2.9 KiB

bc-version domain keywords technologies countries application-area
all
testing
when
single-action
bdd
atdd
given-when-then
flow-test
regression-test
al
w1
all

Keep exactly one WHEN per test, with narrow exceptions for flow and defect-then-fix tests

Description

This is a testing-design practice, not a BC platform requirement — no AL API enforces it, and it should not gate a change the way a platform-contradicted claim would. Each test procedure should contain exactly one [WHEN] block: one action that triggers the behaviour under test. A test with multiple WHENs — "do A, then do B, then check C" — is two or more tests in disguise. Splitting them gives failure isolation (a failing test points at one action, not an ambiguous sequence) and keeps each test readable as a single, falsifiable claim. A precondition action, such as posting a document so a ledger entry exists to assert against, belongs in [GIVEN]; only the action actually being asserted belongs in [WHEN].

Best Practice

Give each test one [WHEN] and one focused claim. A procedure name containing "And" or "Then" in the middle (GetPrice_AndDiscount_ReturnsValues) is a strong signal the test should be split.

See sample: test-one-when-per-test.good.al.

Anti Pattern

A test that performs a first action, then a second unrelated action, then asserts on both — mixing two falsifiable claims into one procedure so a failure can't tell you which action broke.

See sample: test-one-when-per-test.bad.al.

Flow tests — a deliberate exception

A flow test verifies the accumulated outcome of a genuinely multi-round business process (partial receipt then invoicing, several posting rounds against one document), where the sequence itself is the scenario — splitting it would lose the interaction under test. Multiple [WHEN] blocks are allowed only when the procedure name declares the flow, each [WHEN] is labelled as one round of a single scenario rather than an unrelated action, and the [THEN] asserts the accumulated end-state rather than assertions that decompose cleanly per action (if they do decompose cleanly, it is still two tests in disguise). Outside this shape, unit-level tests keep the strict one-WHEN rule.

Defect-then-fix tests — a second, narrower exception

A test that reproduces a specific broken state and then verifies a subsequent action corrects it is not the same shape as an unrelated-action test, even though its [THEN] assertions decompose cleanly per step — clean decomposition is expected here, not a sign of two unrelated tests. This shape is permitted only when the second [WHEN] cannot be meaningfully tested without the first (the fix only affects the exact stale state the first action produced, so splitting would just re-run the first action inside a second test's [GIVEN]), and the procedure name communicates the before/after relationship.