diff --git a/examples/tutorials/bdd.md b/examples/tutorials/bdd.md index f2bd516ef..51fbff4d1 100644 --- a/examples/tutorials/bdd.md +++ b/examples/tutorials/bdd.md @@ -1,5 +1,5 @@ --- -last_modified: 2026-02-03 +last_modified: 2026-05-13 title: "Behavior-Driven Development (BDD)" description: "Implementing Behavior-Driven Development with Deno's Standard Library's BDD module. Create readable, well organised tests with effective assertions." url: /examples/bdd_tutorial/ @@ -262,35 +262,80 @@ These hooks are useful for: - Setting up and tearing down database connections - Creating and cleaning up shared resources -Here's an example of how you might use `beforeAll` and `afterAll`: - -```ts -describe("Database operations", () => { - let db: Database; - - beforeAll(async () => { - // Connect to the database once before all tests - db = await Database.connect(TEST_CONNECTION_STRING); - await db.migrate(); +Here's a runnable example that tests a small HTTP service. Spinning a server up +and down for every single test would be wasteful — and slow, once you have a +handful of cases — so the server starts once in `beforeAll` and shuts down once +in `afterAll`: + +```ts title="user_api_test.ts" +import { afterAll, beforeAll, describe, it } from "jsr:@std/testing/bdd"; +import { assertEquals } from "jsr:@std/assert"; + +describe("User API", () => { + let server: Deno.HttpServer; + let baseUrl: string; + + beforeAll(() => { + // Start the test server once before any test runs. Port 0 asks the + // OS for a free port, so parallel test files don't collide. + server = Deno.serve({ port: 0, onListen() {} }, (req) => { + const { pathname } = new URL(req.url); + if (pathname === "/users/1") { + return Response.json({ id: 1, name: "Ada" }); + } + return new Response("Not Found", { status: 404 }); + }); + const { port } = server.addr as Deno.NetAddr; + baseUrl = `http://localhost:${port}`; }); afterAll(async () => { - // Disconnect after all tests are complete - await db.close(); + // Shut the server down once, after every test has run. Without + // this, `deno test` would report a resource leak. + await server.shutdown(); }); - it("should insert a record", async () => { - const result = await db.insert({ name: "Test" }); - assertEquals(result.success, true); + it("returns a known user", async () => { + const res = await fetch(`${baseUrl}/users/1`); + assertEquals(res.status, 200); + assertEquals(await res.json(), { id: 1, name: "Ada" }); }); - it("should retrieve a record", async () => { - const record = await db.findById(1); - assertEquals(record.name, "Test"); + it("returns 404 for unknown users", async () => { + const res = await fetch(`${baseUrl}/users/999`); + assertEquals(res.status, 404); + await res.body?.cancel(); }); }); ``` +The server binds to a local port, so the test needs network permission to reach +itself: + +```sh +deno test --allow-net=0.0.0.0,localhost user_api_test.ts +``` + +```console +running 1 test from ./user_api_test.ts +User API ... + returns a known user ... ok (4ms) + returns 404 for unknown users ... ok (1ms) +User API ... ok (6ms) + +ok | 1 passed (2 steps) | 0 failed (11ms) +``` + +:::caution + +Anything you create in `beforeAll` is shared by every test in the block. If one +test mutates that shared state — adds a row, changes a field, leaves a handler +registered — the next test starts from the changed state, not the original. When +tests need a clean slate, set up the cheap, per-test state in `beforeEach` and +leave only the expensive, read-mostly setup in `beforeAll`. + +::: + ## Gherkin vs. JavaScript-style BDD If you're familiar with Cucumber or other BDD frameworks, you might be expecting diff --git a/runtime/getting_started/first_project.md b/runtime/getting_started/first_project.md index c00a58380..0ec4f9f46 100644 --- a/runtime/getting_started/first_project.md +++ b/runtime/getting_started/first_project.md @@ -1,5 +1,5 @@ --- -last_modified: 2025-03-10 +last_modified: 2026-05-14 title: Making a Deno project description: "Step-by-step guide to creating your first Deno project. Learn how to initialize a project, understand the basic file structure, run TypeScript code, and execute tests using Deno's built-in test runner." oldUrl: /runtime/manual/getting_started/first_steps/ @@ -35,21 +35,72 @@ my_project A `deno.json` file is created to [configure your project](/runtime/fundamentals/configuration/), and two -TypeScript files are created; `main.ts` and `main_test.ts`. The `main.ts` file -is where you'll write your application code, on initial creation it will contain -a simple program which adds two numbers together. The `main_test.ts` file is -where you can write tests, initially it will contain a test for your addition -program. +TypeScript files are created: `main.ts` and `main_test.ts`. Let's look at what +`deno init` put in each of them, so the rest of this guide makes sense. + +`main.ts` exports an `add` function and, when run as the entry point, prints the +result of calling it. The `import.meta.main` guard means this top-level call +only runs when you execute the file directly — not when another module imports +the `add` function from it: + +```ts title="main.ts" +export function add(a: number, b: number): number { + return a + b; +} + +// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts +if (import.meta.main) { + console.log("Add 2 + 3 =", add(2, 3)); +} +``` + +`main_test.ts` imports the `add` function and asserts it returns the expected +result. `Deno.test` is built into the runtime, so there's no test framework to +install: + +```ts title="main_test.ts" +import { assertEquals } from "@std/assert"; +import { add } from "./main.ts"; + +Deno.test(function addTest() { + assertEquals(add(2, 3), 5); +}); +``` + +`deno.json` is the project's +[configuration file](/runtime/fundamentals/configuration/). The generated one +declares a `dev` task that re-runs `main.ts` whenever a file changes, and pins +`@std/assert` from [JSR](https://jsr.io/@std/assert) so the test above can +resolve it: + +```json title="deno.json" +{ + "tasks": { + "dev": "deno run --watch main.ts" + }, + "imports": { + "@std/assert": "jsr:@std/assert@1" + } +} +``` ## Run your project -You can run this program with the following command: +`cd` into the new directory and run `main.ts` with `deno run`: ```bash -$ deno main.ts +$ cd my_project +$ deno run main.ts Add 2 + 3 = 5 ``` +For an iterative workflow, use the generated `dev` task instead. It runs the +program in watch mode, restarting it every time you save a file: + +```bash +deno task dev +``` + ## Run your tests Deno has a [built in test runner](/runtime/fundamentals/testing/). You can write