# Throws and Writes How to test that a call is refused, and how to test a LiveTable write through a view, as a template would. ## Testing That Code Throws A bare thrown error records a `Crash` and stops the block, so to assert that a call *should* throw, catch it yourself. Verify both that it threw and that the error was the expected kind. ```ts test.ts import { test, session, assert, AuthError } from "@elements/app"; test("posting while logged out is rejected", () => { let threw = false; try { createPost({ title: "hi" }); } catch (err) { threw = true; assert(err instanceof AuthError, `got ${err}`); } assert(threw); }); ``` **Put the error in the message.** Without `` `got ${err}` ``, when the call throws something unexpected (usually a real bug, not the rejection you were testing for), the assertion fails with `assertion failed` and the actual error, its type and its message are discarded. Interpolating `err` costs nothing when the test passes and is the whole diagnosis when it does not. A failed `assert` records and continues, and anchors to the line it is written on. Factor this shape into a helper and every throw-test in the file reports at that one line inside the helper, not at the test that called it. Interpolating the error is what keeps such a helper readable. A helper that takes the call as a callback has to be `async` and `await` it. Once it is `async`, the compiler stops awaiting calls to it, so write `await expectError(...)` in an `async` test. An unawaited helper runs after the test has ended, against a rolled-back savepoint and whatever session the test logged in last, and reports failures that did not happen. The `threw` flag makes the test fail when the call unexpectedly *succeeds*; without it, a call that never throws passes. Use the same shape for `ValidationError` on bad input, `ForbiddenError` on a role check, and so on. ## Testing a LiveTable Write `insert`, `update`, and `delete` work on the server, so a test calls them the same way a template does: on a view. `table.view()` opens one, exactly as a route would, and the write runs the real path: partition check, your handler or auto-SQL, then the broadcast. ```ts test.ts import { test, equal, session, sql } from "@elements/app"; import { comments } from "./services"; test("a member can comment", () => { session.login({ userId: "u1", userName: "alice", }); comments.view().insert({ text: "hi", author: "alice", }); equal(sql(`select text from comments`).firstOrThrow().text, "hi"); }); ``` A partition is passed here too: `comments.view({ roomId })`. The session is the one the test is running in, so a handler's session check sees exactly what a request would. A test that logs nobody in is an anonymous caller, which is how you test that a write is refused: ```ts test.ts test("an anonymous visitor cannot comment", () => { let threw = false; try { comments.view().insert({ text: "nope", author: "nobody", }); } catch (err) { threw = true; assert(err instanceof AuthError, `got ${err}`); } assert(threw); equal(sql(`select count(*) from comments`).firstOrThrow().count, 0); }); ``` Write the mutation somewhere a test can import. A helper defined inside a `template.ehtml` is reachable only from that template, so a write worth testing belongs in the page's `services.ts` or in `app/shared/services/`. Give it the view as a parameter. The template passes the one the route opened, and the test passes one of its own: ```ts // services.ts export function postComment(view: LiveView, text: string) { view.insert({ text, author: session.getOrThrow("userName"), }); } // test.ts postComment(comments.view(), "hi"); ```