Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Testing

[Test] / [TestFixture]

[TestFixture] void Seed() { … } [Test(Seed)] void It_does_the_thing() { Assert.Equal(…); }

A test is an ordinary function marked [Test]. It runs against a throwaway clone of the app, so it may create rows and break rules freely. A [TestFixture] seeds the data once and every test forks from it.

stable4 examples compiled by CItesting

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 data

Description#

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#

Related

Assert

The assertions a test makes. Beyond the usual equality and null checks there are comparisons (Greater, Less, InRange)…

runas

Runs a block as a given principal, so security rules apply exactly as they would for that user. It is how you test that…

Running tests

Runs your app's tests against a throwaway copy of its database, reporting each test as it finishes. Your local source…

Running tests locally

Runs your app's tests against a Platform on your own machine — no account, no network, no setup beyond a running local…

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…