Summary#
A test is an ordinary function marked [Test]. It runs against a throwaway clone of the app — its own database,
made for it and thrown away after — so it can create rows, violate rules and assert on the wreckage without touching
anything real, and without cleaning up after itself.
A [TestFixture] builds the starting data once; every test that names it forks from that state.
Signature#
[TestFixture]
void <Seed>() { … } // build the starting data, once
[Test]
void <Name>() { … } // a test with no fixture
[Test(<Seed>)]
void <Name>() { … } // a test that starts from <Seed>'s dataDescription#
A test is a function#
There is no separate test language. A test is a function: it can call your functions, create rows, run queries — the whole model is in scope.
entity Order {
[Required] string Code;
decimal Total;
security { allow create, read when IsAuthenticated || IsAnonymous; } // an entity with no security block is denied to everyone
}
void PlaceOrder(string code, decimal total) {
var o = new Order { Code = code, Total = total };
}[Test]
void Placing_an_order_stores_its_total() {
PlaceOrder("A1", 42m);
Assert.Equal(42m, Order.Single(o => o.Code == "A1").Total);
}A fixture seeds once; tests fork from it#
Building the same three customers at the top of nine tests is slow and, worse, it is nine places to change. A
[TestFixture] builds them once:
[TestFixture]
void Seeded() {
PlaceOrder("A1", 100m);
PlaceOrder("A2", 50m);
}
[Test(Seeded)]
void The_seeded_orders_are_there() {
Assert.Equal(2, Order.Count());
}
[Test(Seeded)]
void A_new_order_is_not_seen_by_its_siblings() {
PlaceOrder("A3", 5m);
Assert.Equal(3, Order.Count()); // this test's own clone: the seeded two, plus this one
}The second test creates a third order and sees three. The first test still sees two. Tests never see each other's writes — each forks its own copy of the fixture's data, so they can run in any order, or at the same time, and neither has to undo anything.
That isolation is the whole point: a test suite where one test's leftovers change another's outcome is a suite that fails at random and gets ignored.
The fixture is unsecured; the test body is not#
They do not run under the same rules, and this catches everyone once:
- a
[TestFixture]seeds unsecured — it can create rows across every entity, including ones nobody is allowed to create, so building a scenario never means weakening a rule; - a
[Test]body runs secured, as an initially-anonymous principal — your rules are on, and nobody is signed in.
So a test that simply calls a function may be denied, and that is the system working. To act as a real user, name a principal and run as them ([runas(Name)] test attribute and principal selectors). The whole model is in the testing guide.
What should a test be called?#
A test's name is read by a person deciding whether the failure matters. A_new_order_is_not_seen_by_its_siblings
tells them; Test3 does not.
Parking a test with [Skip]#
A test that cannot run yet — it depends on a capability the platform doesn't offer, or an intended behaviour that
isn't built — is not deleted. Marking the [Test] with [Skip("reason")] keeps it in the suite as the executable
record of the intended behaviour: it is still discovered and listed, but never run, and every surface renders
it as a skip. The reason string is required — it is the visible note of why the test is parked:
[Test]
[Skip("KNOWN-GAP: refunds aren't implemented yet")]
void A_refund_restores_stock() {
// The intended behaviour, written out — it compiles, so it can't rot, and it turns on
// the day the gap closes and the [Skip] comes off.
Assert.Equal(0, Order.Count());
}[Skip] applies only to a [Test] (a [TestFixture] cannot be skipped), and its body must still compile — the
whole value of a parked test is that it is a real, type-checked spec, not a comment.
See also#
- Testing (real app, real data, real rules) — the testing guide: isolation, who a test acts as, and how you prove a rule
- Assert — the assertions, including
Assert.ThrowsandAssert.Denied - [runas(Name)] test attribute and principal selectors —
principaldeclarations and[runas(Name)] - runas — running a test as a particular user, to test security
- Running tests locally —
osy test