# Assert

> The assertions a test makes. Beyond the usual equality and null checks there are comparisons (Greater, Less, InRange), a regex check (Matches), collection checks (NotEmpty, Count), a general predicate (That) — and the two that earn their keep: Assert.Throws proves a rule is enforced and Assert.Denied proves security is.

<!-- id: testing-assert · area: testing · stability: stable · html: https://osysharp.com/reference/testing/assert/ -->

## Summary        {#summary}
`Assert.*` is what a test claims. The everyday ones are equality and null checks; the two that earn their keep are
**`Assert.Throws`** (a rule really is enforced) and **`Assert.Denied`** (a principal really cannot do it).

## Signature      {#signature}
```osy syntax
Assert.Equal(<expected>, <actual>)      Assert.NotEqual(<a>, <b>)
Assert.Equal(<expected>, <actual>, <precision>)                             // equal to N decimal places
Assert.True(<bool>)                     Assert.False(<bool>)
Assert.Null(<value>)                    Assert.NotNull(<value>)
Assert.Contains(<needle>, <haystack>)   Assert.StartsWith(<prefix>, <text>)
Assert.Greater(<a>, <b>)                Assert.Less(<a>, <b>)               // a > b · a < b
Assert.InRange(<value>, <low>, <high>)                                      // low <= value <= high, inclusive
Assert.Matches(<pattern>, <text>)                                           // the text matches the regex pattern
Assert.NotEmpty(<collection>)           Assert.Count(<collection>, <n>)     // at least one · exactly n
Assert.That(<bool condition>, <value>)                                      // condition holds; value shown on failure
Assert.Throws(() => <expression>)       // the code faults — with ANY fault
Assert.Throws<T>(() => <expression>)    // …and it is a `T`; RETURNS the fault, so `.Message` / `.Type` read
Assert.Denied(() => <expression>)       // the acting principal is refused
Assert.Throws(() => { <statements> })   // a SEQUENCE that should fault (block body)
Assert.Denied(() => { <statements> })   // a sequence the acting principal is refused
Assert.Refuses(<assertion>)             // that ASSERTION fails — the one you point at a rule you want enforced
Assert.Refuses(<assertion>, <message>)  // …and the refusal says this (CONTAINS, not word for word)
```

## Description    {#description}

### The four you will reach for every time   {#everyday}
```osy title="asserting on what a function did" test app=testing-assert
entity Order {
  [Required] string Code;
  decimal Total;
  bool Cancelled;
}

void PlaceOrder(string code, decimal total) {
  var o = new Order { Code = code, Total = total };
}

[Test]
void An_order_starts_uncancelled() {
  PlaceOrder("A1", 42m);
  var o = Order.Single(x => x.Code == "A1");

  Assert.Equal(42m, o.Total);        // expected first, actual second — as in xUnit
  Assert.False(o.Cancelled);
  Assert.NotNull(o.Code);
}
```

`Assert.Equal(expected, actual)` — expected first. Get it backwards and the test still passes; it is the *failure
message* that lies to you, which you will discover at the worst moment.

### Comparing a `double` — `Assert.Equal(expected, actual, precision)`   {#precision}
The third argument is the number of **decimal places** both sides are rounded to before comparing — xUnit's own
overload. Reach for it whenever the value is a `double`:

```osy syntax
Assert.Equal(0.0, Math.Sin(Math.PI), 12);
Assert.Equal(2.0, Math.Log(Math.Exp(2)), 12);
```

`Math.Sin(Math.PI)` is **not** exactly 0, and `Math.Log(Math.Exp(2))` is not exactly 2 — in any language. π and e are
not representable as doubles, so every transcendental inherits the error of its input. The tolerance is not
sloppiness; it is the only correct way to assert one.

A `decimal` pair rounds **as decimal**, never through a double — money is exactly where a detour through binary
floating point would reintroduce the imprecision you are trying to tolerate. And the two-argument form stays
**exact**: the precision widens the comparison only where you ask for it.

### `Assert.Throws` — prove the rule bites   {#throws}
A rule you never tested is a rule you *hope* you wrote. Assert that breaking it actually fails:

```osy title="an invariant is really enforced" test app=testing-assert
entity Account {
  [Required] string Holder;
  decimal Balance;
  invariant Balance >= 0;
}

[Test]
void A_negative_balance_is_refused() {
  Assert.Throws(() => new Account { Holder = "Ada", Balance = -1m });
}
```

Without this test, deleting the `invariant` line breaks nothing that anyone notices — until a balance goes negative in
production.

### `Assert.Throws<T>` — and prove it is the RIGHT fault   {#throws-typed}
`Assert.Throws(…)` holds for **any** fault, which is often one claim too weak: a test that means "this is refused
because the row already exists" also passes when the code under test throws because a field is null. Name the type
and the assertion says which:

```osy title="the type is part of the claim" test app=testing-assert
entity Invitation {
  [Required, Unique, MaxLength(40)] string Code;
}

[Test]
void A_duplicate_code_is_refused() {
  var first = new Invitation { Code = "welcome" };
  var ex = Assert.Throws<ValidationException>(() => new Invitation { Code = "welcome" });
  Assert.Contains("Code", ex.Message);
}
```

Two things it gives you that the untyped form does not. It **fails on the wrong fault** — if the code throws a
`NotFoundException`, the test says so by name instead of passing. And it **returns the fault**, so you can go on to
assert on `ex.Message` (what a human will read) and `ex.Type` (its name as a string).

The type argument is the same closed set `throw` uses ([throw](https://osysharp.com/reference/function/throw/)), and only these nine names:

| Type | What raises it |
|---|---|
| `Exception` | the base — matches every one below, exactly like the untyped form |
| `NotFoundException` | you asked for something that does not exist (app-raised) |
| `ValidationException` | **the entity's own declared rules refused the write** — a broken `[Unique]` (a duplicate row, single-column or composite), `[Required]`, `[Pattern]`, `[MaxLength]`, `[Min]`, `[Max]`, or an invariant. The platform raises this for you at the commit, so it is the one most tests assert |
| `ConflictException` | the CURRENT STATE of the data refuses it — two writers colliding. **Not** a broken constraint, however much English calls a double booking a conflict |
| `OAuthConnectionFailedException` | an external authorization handshake failed |
| `NotAuthorized` | engine-raised — a workflow event's `[Authorize]`, or a slot's candidate gate, said no |
| `RequirementsNotMet` | engine-raised — a slot deposit's `Requires` criteria are not met yet |
| `WorkflowError` | engine-raised — an awaited child workflow reached a `terminal error` |
| `WorkflowCancelled` | engine-raised — an awaited child workflow reached a `terminal cancel` |

Anything else is a compile error that lists the set with these same meanings, so you cannot misspell your way into a
silent catch-all.

⚠ A **security** refusal is [[testing-assert#denied|`Assert.Denied`]], not a type argument here — there is no
`SecurityException` in the set, deliberately.

The block-body form works the same way: `Assert.Throws<ValidationException>(() => { … })`.

### `Assert.Denied` — prove security bites   {#denied}
The security equivalent, and the most valuable assertion in the set. It claims that the **acting principal is
refused** — not that the code faulted, but that it was *not allowed*:

```osy title="the model: a memo only its owner may read" test app=testing-assert
[Principal] entity User {
  [Required] string Name;
}

entity Memo {
  User Owner;
  [MaxLength(200)] string Note;
  security {
    allow read when IsAuthenticated;      // colleagues can SEE it …
    allow update where Owner == user;     // … only the owner may change it
  }
}

void Annotate(Guid memoId, string note) {
  var m = Memo.Single(x => x.Id == memoId);
  m.Note = note;
}

```

The test — and every line of the setup is placed so that it cannot be the thing that fails:

```osy title="a user cannot touch another user's row" run app=testing-assert
// A `principal` resolves UNSECURED, which is what makes it usable here: a `[Test]` body outside a `runas` is an
// anonymous caller, so a `User.Single(…)` written there reads nothing and there is nobody to become.
principal Alice => User.Single(u => u.Name == "Alice");
principal Bob   => User.Single(u => u.Name == "Bob");

[TestFixture]
void Seed() {
  var alice = new User { Name = "Alice" };
  var bob = new User { Name = "Bob" };
  var memo = new Memo { Owner = alice, Note = "original" };
}

[Test(Seed)]
[runas(Alice)]
void Bob_cannot_annotate_Alices_memo() {
  var memo = Memo.Single(m => m.Owner == Alice);

  runas(Bob) {
    // Bob can READ this row — that is deliberate, and it is what makes the assertion mean something. The only
    // thing he may not do is WRITE it, so the refusal `Assert.Denied` catches can only be the update rule.
    Assert.NotNull(Memo.FirstOrDefault(m => m.Id == memo.Id));
    Assert.Denied(() => Annotate(memo.Id, "hijacked"));
  }
}
```

This is the test that stops a refactor from quietly opening a door. Write one for every rule that matters — see
[runas](https://osysharp.com/reference/testing/runas/).

Notice where the setup lives: Alice, Bob and the memo are created by the fixture, and the row is looked up as
**Alice** before the `runas(Bob)`. Notice too that `Memo` is READABLE by any colleague — if a non-owner could not
even see the row, `Annotate` would fail looking it up and `Assert.Denied` would pass on a refusal that has nothing
to do with the update rule the test names. Only the one thing that must be refused is inside `Assert.Denied`. That is not style — it is what makes the
assertion mean anything.

**When an UPDATE is refused.** A write is judged when it reaches the store, not when the property is assigned — so
inside `Assert.Denied` use the block form and commit in it: `Assert.Denied(() => { memo.Note = "x"; UnitOfWork.Commit(); })`.
A bare assignment with no commit inside the lambda is not yet a write the rule can refuse, and the assertion would
report that nothing was denied.

> **A denial test is the one test whose green tells you nothing by itself.** Every other assertion proves it ran by
> producing the right answer; this one is satisfied by *any* refusal on the way to the thing you are testing. Build a
> prerequisite inside the lambda and the platform may refuse **that** instead — the test passes, the rule you named
> was never evaluated, and nothing distinguishes the two.
>
> So: everything in the setup must be independently known-**allowed** for the acting principal. Seed prerequisites in
> a `[TestFixture]`, or create them as a principal already permitted to. And pair a denial with a **positive twin**
> that does the same thing successfully — if both fail the same way, the setup is what you are testing.

```osy title="the denial that proves nothing" syntax
// ✗ Creating the User is itself a write Alice may be refused for. If it is, the denial fires there
//   and Membership's rule is never reached — green, and about nothing.
runas(alice) {
  Assert.Denied(() => new Membership { Member = new User { Name = "Mallory" } });
}
```

`osy lint` reports this as **`testing-denial-provable-by-its-setup`**: a denial whose lambda constructs more than one
entity. Constructing exactly the subject is the correct shape and is never flagged.

### A block body — a sequence that should fault   {#block-body}
When the code you expect to fail is more than one expression — set something up, *then* do the thing that must be
refused — give `Assert.Throws` / `Assert.Denied` a **block** lambda instead of a single expression:

```osy title="a block body: arrange, then the act that must fault" test app=testing-assert
entity Ledger {
  [Required] string Name;
  decimal Balance;
  invariant Balance >= 0;
}

[Test]
void An_overdraw_is_refused() {
  var l = new Ledger { Name = "ops", Balance = 100m };
  Assert.Throws(() => {
    var current = l.Balance;      // a local
    l.Balance = current - 250m;   // the write that trips `invariant Balance >= 0`
  });
}
```

The block runs its statements in order and the assertion holds if **any** of them faults. Keep it a simple sequence —
locals, assignments and calls (the work you expect to throw). Control flow (`if`, `foreach`) and `return` don't belong
in a deferred-assert block; if you need them, put them in a helper function and call it inside the lambda.

A block lambda is accepted **only** in these deferred-assert positions. Everywhere else — notably a query predicate
like `Order.Where(o => …)`, whose body lowers to SQL — a lambda takes an **expression** body (`o => o.Total > 0`), and a
block there is a compile error that says so.

### Comparisons, collections, and a general predicate   {#more}
Beyond equality there is a small kit for the everyday shapes of a claim:

- **`Assert.Greater(a, b)`** and **`Assert.Less(a, b)`** — the first value is strictly greater / less than the second.
- **`Assert.InRange(value, low, high)`** — the value lies within `[low, high]`, **bounds included**.
- **`Assert.Matches(pattern, text)`** — the `text` matches the regular expression `pattern` — pattern first
  ([Regex](https://osysharp.com/reference/stdlib/regex/)).
- **`Assert.NotEmpty(collection)`** — the collection has at least one element; its opposite is `Assert.Empty`, and
  `Assert.Single` claims exactly one.
- **`Assert.Count(collection, n)`** — the collection has exactly `n` elements. This is the honest way to assert "the
  query returned three rows", and under a security rule it counts only the rows the acting principal may see.
- **`Assert.That(condition, value)`** — the escape hatch: assert an arbitrary boolean `condition` (first), with a
  `value` carried along to appear in the failure message. Use it for a claim none of the named assertions captures.

```osy title="the comparison and collection assertions, run" run app=testing-assert
[Test]
void Comparison_and_collection_assertions() {
  Assert.Greater(5, 3);                        // 5 > 3
  Assert.Less(3, 5);                           // 3 < 5
  Assert.InRange(5, 1, 10);                    // within [1, 10]
  Assert.InRange(1, 1, 10);                    // the bounds are inclusive
  Assert.Matches("[a-z]+[0-9]+", "abc123");    // pattern first: "abc123" matches the regex

  var parts = Text.Split("a,b,c", ",");        // a List<string> of three
  Assert.NotEmpty(parts);
  Assert.Count(parts, 3);                      // exactly three elements

  var total = 42m;
  Assert.That(total > 0m && total < 100m, total);   // condition first; `total` is shown if it fails
}
```

Prefer a named assertion when one fits — `Assert.Count(rows, 3)` reads better and fails with a clearer message than
`Assert.That(rows.Count == 3, rows.Count)`. Keep `Assert.That` for the claim that has no better name.

### The assertions that read the SCREEN   {#ui}

These nineteen live on the same `Assert.` and are absent from everything above — a test that drives the UI reaches
for them, and a reader who came here for "what can I assert?" would otherwise conclude they do not exist. Their
detail, and the `Ui.*` verbs that drive the screen they read, are in [Ui — drive the app's UI from a test](https://osysharp.com/reference/testing/ui/).

| assertion | says |
|---|---|
| `Assert.Visible(text)` | the text is rendered on the current screen |
| `Assert.Hidden(text)` | it is not |
| `Assert.TextIs(text)` | some element reads EXACTLY this — not merely contains it |
| `Assert.OnPage(path)` | the app is currently showing this route |
| `Assert.Value(field, expected)` | the field this label names holds this value |
| `Assert.Enabled(control)` · `Assert.Disabled(control)` | the control this label names is (not) operable |
| `Assert.DisabledBecause(control, reason)` | it is not operable, and explains itself with this sentence |
| `Assert.Items(container, expected)` | the list this label names shows exactly this many items |
| `Assert.Dialog(title)` | a dialog is open, and this is its title |
| `Assert.Checked(field[, expected])` | the checkbox or toggle is in this state — **checked** unless you pass `false` |
| `Assert.Expanded(control[, expected])` | the disclosure control is open (or closed) — **open** unless you pass `false` |
| `Assert.Selected(option[, expected])` | the option says it is the chosen one (or is not) — **chosen** unless you pass `false` |
| `Assert.Focused(control)` | this is the control the keyboard is on |
| `Assert.Before(first, second)` | the first value's row is rendered ABOVE the second's — the assertion a SORT needs |
| `Assert.Cell(row, column, expected)` | that row's cell under this column header reads this |
| `Assert.Probe(control, field, expected)` | a foreign control reports this field of its `probe { }` block as this |
| `Assert.Flow(container, direction)` | that container lays its children out `"across"` or `"down"` |
| `Assert.Violation(field[, message])` | this field is refused, and the message it shows contains this |

**Any of them may be scoped to one region** with `within:` — `Assert.Value("Name", "Ada", within: "Edit book")` —
except the three a region cannot narrow: `OnPage` (a route is not inside a container), `Dialog` (a modal is
page-level), and `Violation`. `Ui.Within(container) { … }` scopes a whole block at once.

## See also       {#see-also}
- [Layout assertions — is it actually usable on screen?](https://osysharp.com/reference/testing/ui-layout/) — the geometric assertions (`Assert.Clickable`, `Assert.FitsOn`, `Assert.Above`, …), which need a renderer with a compositor and report NOT CHECKED without one
- [Ui — drive the app's UI from a test](https://osysharp.com/reference/testing/ui/) — the `Ui.*` verbs, and the detail of every screen-reading assertion above
- [Regex](https://osysharp.com/reference/stdlib/regex/) — the pattern language `Assert.Matches` uses
- [[Test] / [TestFixture]](https://osysharp.com/reference/testing/test/) — the `[Test]` functions these assertions live in
- [runas](https://osysharp.com/reference/testing/runas/) — acting as a principal, so `Assert.Denied` has someone to deny
- [invariant](https://osysharp.com/reference/entity/invariants/) — the rules `Assert.Throws` proves you wrote
