Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Why Osy# · Chapter 06 · Testing

An agent can prove its code works. Not say so.

There is nothing to mock because there is nothing in the way: your functions, your entities, your rules, on a real Postgres that starts for you. A test runs as a named person, the same file drives a real browser and looks at the pixels, and a parked run can be rewound to the step that went wrong. So the code an agent wrote is not code you take on trust. It was tested the way nobody bothers to test by hand.

In practiceThe agent writes the test beside the feature, runs one command, and hands you a green that ran real functions against real rules on a real database, and looked at the screen.You stop building
  • mocks
  • fixtures that lie
  • a test database you maintain
  • a second framework for the screen
  • a "trust me" from the agent
demo/ember/tests/pages.test.osyverbatim — this file compiles
[Test(TwoPeople)]
void an_anonymous_visitor_is_sent_to_the_door() {
  Ui.Visit("/");

  Assert.OnPage("/login");
  Assert.Visible("a small room for a small team");
}

[Test(TwoPeople)]
void the_door_offers_both_ways_in() {
  Ui.Visit("/login");

  Assert.Visible("Sign in");
  Assert.Visible("Create account");
}

[Test(TwoPeople)]
void a_signed_in_person_with_no_channels_is_told_how_to_make_one() {
  Ui.SignInAs(Ada);
  Ui.Visit("/");

  Assert.Visible("Pick a channel");
  Assert.Visible("none yet — make one below");
}

01

There is nothing to mock, because there is nothing in the way

No repository to stub, no in-memory database to configure, no test double for your own model. A test is an ordinary function marked [Test]: it calls your real functions, against your real entities, with your real rules switched on — on its own private copy of the database, thrown away when it finishes.

demo/ember/tests/pages.test.osyverbatim — this file compiles
1[TestFixture]
void TwoPeople() {
  new User { Email = "[email protected]", PasswordHash = Security.HashPassword("x"), DisplayName = "ada" };
  new User { Email = "[email protected]",  PasswordHash = Security.HashPassword("x"), DisplayName = "bo" };
}

2principal Ada => User.Single(u => u.Email == "[email protected]");
principal Bo  => User.Single(u => u.Email == "[email protected]");

[Test(TwoPeople)]
3void an_anonymous_visitor_is_sent_to_the_door() {
4  Ui.Visit("/");

  Assert.OnPage("/login");
  Assert.Visible("a small room for a small team");
}

[Test(TwoPeople)]
void the_door_offers_both_ways_in() {
  Ui.Visit("/login");

  Assert.Visible("Sign in");
  Assert.Visible("Create account");
}

[Test(TwoPeople)]
void a_signed_in_person_with_no_channels_is_told_how_to_make_one() {
  Ui.SignInAs(Ada);
  Ui.Visit("/");

  Assert.Visible("Pick a channel");
  Assert.Visible("none yet — make one below");
}
1

The fixture seeds once. It runs UNSECURED — it is scaffolding, so setting up a scenario never requires weakening a rule.

2

A name for a seeded row. This is what a test can then ACT AS. It binds and never creates: the selector has to find a row the fixture made.

3

A test body runs secured, as a stranger — nobody is signed in until you say otherwise, so a denial is a real denial.

4

The same test drives the screen. No second framework, no selector language, no second process to start — see §4.

02

Every test gets its own database

A fixture seeds the starting data once; every test that names it forks its own private clone of that state. Two tests under one fixture never see each other's writes — in any order, at the same time. A suite where one test's leftovers change another's outcome is a suite that fails at random and then gets ignored.

the whole suite of a real app, in one command
$ osy test
Starting a local platform for this project…

 a_note_is_invisible_to_everyone_but_its_owner (908ms)
 an_owner_still_sees_her_own_note (967ms)
 a_note_planted_for_somebody_else_is_not_even_EXPRESSIBLE (967ms)
 an_ITEM_is_invisible_when_its_list_is (627ms)
 an_item_cannot_be_added_to_somebody_elses_list (1103ms)
  … 16 more

✓ 21 passed.
⚠ the app's test set CHANGED since the last run — 24 declared then, 21 now:
    gone: shot_recall_lists, shot_recall_note, shot_recall_notes
  A count that holds still over a set that moved reads as "still green" while
  coverage moves.

Real output. That last warning is the runner noticing that the number stayed the same while the tests underneath it did not — the failure mode a green tally cannot show you.

The VS Code Test Explorer running an Osy# suite: six green tests grouped by file and fixture
The same tests from the editor. The tree is FILE → FIXTURE → TEST — the fixture is a GROUP, because it seeds the branch the two tests under it fork from — and the run happens against a local platform the CLI starts for you. Same tests, same ids, no second runner.

03

The rules are the one thing nobody else can test for you

A security rule that is too strict fails loudly the first time somebody uses the app. A rule that is too loose fails silently, forever. So a test declares a principal and runs as them — and the interesting half is not the refusal.

Apps/recall-osy/tests/isolation.test.osyverbatim — this file compiles
[Test(TwoPeople)]
1[runas(Bo)]
void a_note_is_invisible_to_everyone_but_its_owner() {
2  Assert.Null(Note.FirstOrDefault(n => n.Title == "Ana's private note"));
3  // …and not by title alone: a listing must not leak it either.
  Assert.Equal(1, Note.Where(n => n.Title != "").Take(50).ToList().Count);
}
1

Run as somebody. The rule is inside the query, so Ana's note is not hidden from Bo — it is not selected.

2

The refusal: the intruder cannot reach it by name.

3

…nor by listing, which is the leak a name-lookup test misses.

4

⭐ The positive half, and it is not ceremony. A rule that denied EVERYONE passes every refusal test ever written and leaves the app broken. An all-negative suite proves the data is unreachable, not that it is protected.

04

The same test drives the screen

Ui.Visit opens a route, Ui.Click presses what a person would press, and the same Assert.* verbs ask the questions. It is one file and one language — the test that checks a security rule and the test that fills in a form are the same kind of thing.

demo/ember/tests/pages.test.osyverbatim — this file compiles
void a_message_you_send_appears_in_the_transcript_it_arrived_on() {
  Guid id = Guid.Empty;
1  runas (Ada) { id = CreateChannel("design", "how things look").Id; }

2  Ui.SignInAs(Ada);
  Ui.Visit("/c/" + id);

  Assert.Visible("Nothing here yet.");

3  Ui.Fill("Message", "morning — pushing the new rail today");
  Ui.Click("Send");

  Assert.Visible("morning — pushing the new rail today");
  Assert.Visible("ada");
4  Assert.Hidden("Nothing here yet.");
}
1

Set the scene through the app's own functions, as somebody. Not through a fixture file, and not through the database.

2

The browser is now signed in as that principal — the same one the server-side half of the test used.

3

Addressed by what a person SEES. No CSS selector, no test id, nothing in your markup that exists only for the test.

4

And the empty state is gone — the assertion that catches a list which appends without clearing.

05

…and a tier that can see pixels

Every assertion so far is about TEXT or STATE, and all of them pass on a screen that is visually broken. osy test --pixels runs the same file in a real browser, where four more questions become answerable — and the difference is not academic.

Here is a checkout page. A promo ribbon ships, positioned over the card. The test is three assertions long and it is the test you would have written.

void the_confirm_button_is_there_and_a_person_can_press_it() {
  Ui.Visit("/");

  Assert.Visible("Confirm");        // text: on the page
  Assert.Enabled("Confirm");        // state: not disabled
  Assert.Clickable("Confirm");      // geometry: a press actually reaches it
}
$ osy test
    │ Assert.Clickable(…) — NOT CHECKED: this run renders in happy-dom, which has no
      compositor — every box it reports is 0×0, so nothing here can judge what covers
      what. Run the same tests with `osy test --pixels` to check them in a real browser.
 the_confirm_button_is_there_and_a_person_can_press_it (1361ms)

✓ 1 passed.

Green — and honest about it. The claim it could not judge says so by name, in the run, rather than passing quietly.

$ osy test --pixels
 the_confirm_button_is_there_and_a_person_can_press_it (2089ms) — at line 7:3
    Assertion failed: Assert.Clickable(…) failed: a click at 'Confirm's own centre
    (640, 140) lands on <div> "half price today" instead — something is covering it.
    That is what a person pressing it would hit, so the control is on screen and
    unusable.

✗ 0 passed, 1 failed.
  📷 1 screenshot(s) — one per UI test, with that test's own data — in osy-shots/

Same file, same three assertions, real Chromium. The button renders, is enabled, is correctly labelled — and nobody can press it.

And it is how an agent gets eyes. Every UI test under --pixels photographs its own page, with its own data, into osy-shots/ — and the run prints the absolute path. Something writing your code can then open the PNG and look at what it built, which is a different kind of evidence from a passing assertion: an assertion checks the thing you thought to check, and a picture shows the thing you did not.

the last line of a --pixels run
  📷 3 screenshot(s) — one per UI test, with that test's own data — in
     /Users/you/projects/shop/osy-shots

Measured, and the absolute path turned out to be the whole of it: an agent read this line, listed that directory, opened one of the PNGs and reported what it saw — "labeled fields, the accent color on the primary action, a status badge, and a working switch". Nobody told it to.

What the browser tier adds

Assert.Clickable
geometry
A click at the control's own centre reaches IT. Names what a press would hit instead, with the coordinates.
Assert.FitsOn
geometry
Nothing is cut off. textContent holds the whole string whether the box shows it or not, so a truncated label is invisible to every other assertion in the language.
Assert.Inside · Assert.NoOverflow
geometry
Inside its own card; nothing pushes the page wider than the window.
Assert.Above · LeftOf · Wider · SameWidth
geometry
Where things are, relative to each other — so they survive a restyle, a font update and a browser version. There is deliberately no Assert.Equal(200, width).
Ui.Shot("label")
a photograph
Writes a PNG of the page with that test's own data, at a point you choose. Asserts nothing — it is how you, or the agent writing your code, LOOK at what the app renders.
--headed
a window
Show the browser, slowed down, and watch the test drive the page.

06

Time, and the network

The two things a suite normally has to pretend about.

…and does not have to here

A reminder that fires in two days
no waiting
TestClock.Advance(TimeSpan.FromDays(2)) moves every clock the app reads — a function body, the instant bound into a SQL Where, and the browser. Three renderings of one question, and a test may only get one answer to it.
An outbound HTTP call
it really runs
A [Test] has the capabilities the app has, so the call is MADE. That is the one thing the rest of the suite cannot tell you: compile, lint, render and assert all pass just as happily when the URL is dead or the key is missing.
A test you cannot write yet
parked, loudly
[Skip("reason")] — and the reason is required. The parked test is the executable statement of what you meant, and it surfaces as a real skip rather than vanishing.

What happens when you change it

Testing changes, and what they cost

Each of these is a thing you would do on a Tuesday. The verdict is the compiler's, not a convention's.

You do thisVerdictBecause
Rename an entity field a test readscompilesOne rename. The test is Osy# against your model, so a stale name is a compile error rather than a mock that keeps agreeing with you.
Filter to one testcompilesosy test --test 'file::fixture::name'. A filter that matches NOTHING is refused, with the declared names — a selection that matches nothing runs zero tests, reports zero failures, and reads exactly like green.
Add a layout assertion to an existing testcompilesIt runs in both tiers. Under osy test it reports NOT CHECKED; under --pixels it is judged.
Tighten a security rulecompilesThe three tests that pin it tell you which side you moved — and the positive one tells you if you went too far, which is the failure a refusal test cannot see.
Have one test depend on another's datarefusedNot expressible. Each test forks its own clone of the fixture, so there is no shared state to lean on and no ordering to get right.