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.
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"); }
The fixture seeds once. It runs UNSECURED — it is scaffolding, so setting up a scenario never requires weakening a rule.
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.
A test body runs secured, as a stranger — nobody is signed in until you say otherwise, so a denial is a real denial.
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.
$ 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.

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.
[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); }
Run as somebody. The rule is inside the query, so Ana's note is not hidden from Bo — it is not selected.
The refusal: the intruder cannot reach it by name.
…nor by listing, which is the leak a name-lookup test misses.
⭐ 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.
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."); }
Set the scene through the app's own functions, as somebody. Not through a fixture file, and not through the database.
The browser is now signed in as that principal — the same one the server-side half of the test used.
Addressed by what a person SEES. No CSS selector, no test id, nothing in your markup that exists only for the test.
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.
📷 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.ClickableAssert.FitsOntextContent 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.NoOverflowAssert.Above · LeftOf · Wider
· SameWidthAssert.Equal(200, width).Ui.Shot("label")--headed06
Time, and the network
The two things a suite normally has to pretend about.
…and does not have to here
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.[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.[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
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.osy test it reports NOT CHECKED; under --pixels it is judged.Where to go next
Run and debug these from the gutter, and step through one.
Every assertion, every driver verb, every flag.
The rules §3 is proving.