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

Guides

Testing

There is nothing to mock, because there is nothing in the way. A test is your app — your functions, your entities, your security rules — on a throwaway copy of the database. And the same file can drive the screen in a real browser, where four questions no text assertion can answer become answerable.

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 = "ada@ember.test", PasswordHash = Security.HashPassword("x"), DisplayName = "ada" };
  new User { Email = "bo@ember.test",  PasswordHash = Security.HashPassword("x"), DisplayName = "bo" };
}

2principal Ada => User.Single(u => u.Email == "ada@ember.test");
principal Bo  => User.Single(u => u.Email == "bo@ember.test");

[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.

07

What happens when you change it

Testing changes, and what they cost

Rename an entity field a test reads
compiles
One 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 test
compiles
osy 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 test
compiles
It runs in both tiers. Under osy test it reports NOT CHECKED; under --pixels it is judged.
Tighten a security rule
compiles
The 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 data
refused
Not 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.

Where to go next

The editor

Run and debug these from the gutter, and step through one.

Testing reference

Every assertion, every driver verb, every flag.

Security

The rules §3 is proving.