Skip to main content
On this page

Deno 是一个在浏览器之外运行的 JavaScript 运行时,因此,你 无法像在浏览器中那样直接在 Deno 中操作文档对象模型(DOM)。然而你可以使用类似 deno-dom, JSDomLinkeDOM 这样的库来处理 DOM。本教程将指导你如何使用 Deno 有效地测试你的 Web 应用。

测试 UI 组件和 DOM 操作 Jump to heading

假设你有一个展示用户资料的网站,你可以设置一个测试 函数来验证 DOM 元素创建是否正常工作。这段代码 先设置一个基础的卡片元素,然后测试创建出来的 DOM 结构是否与 预期一致。

import { assertEquals } from "jsr:@std/assert";
import { DOMParser, Element } from "jsr:@b-fuze/deno-dom";

// 进行 DOM 操作的组件或函数
function createUserCard(user: { name: string; email: string }): Element {
  const doc = new DOMParser().parseFromString("<div></div>", "text/html")!;
  const card = doc.createElement("div");
  card.className = "user-card";

  const name = doc.createElement("h2");
  name.textContent = user.name;
  card.appendChild(name);

  const email = doc.createElement("p");
  email.textContent = user.email;
  email.className = "email";
  card.appendChild(email);

  return card;
}

Deno.test("DOM 操作测试", () => {
  // 创建测试用户
  const testUser = { name: "测试用户", email: "test@example.com" };

  // 调用函数
  const card = createUserCard(testUser);

  // 断言 DOM 结构正确
  assertEquals(card.className, "user-card");
  assertEquals(card.children.length, 2);
  assertEquals(card.querySelector("h2")?.textContent, "测试用户");
  assertEquals(card.querySelector(".email")?.textContent, "test@example.com");
});

测试事件处理 Jump to heading

Web 应用通常通过事件来处理用户交互。下面介绍如何测试事件处理函数。此代码会创建一个按钮,用于跟踪其激活/未激活状态,并在被点击时更新其外观。配套的测试通过创建按钮、检查其初始状态、模拟点击,并在每次交互后断言按钮确实正确更新状态,从而验证切换功能:

import { DOMParser } from "jsr:@b-fuze/deno-dom";
import { assertEquals } from "jsr:@std/assert";

// 带有事件处理的组件
function createToggleButton(text: string) {
  const doc = new DOMParser().parseFromString("<div></div>", "text/html")!;
  const button = doc.createElement("button");

  button.textContent = text;
  button.dataset.active = "false";

  button.addEventListener("click", () => {
    const isActive = button.dataset.active === "true";
    button.dataset.active = isActive ? "false" : "true";
    button.classList.toggle("active", !isActive);
  });

  return button;
}

Deno.test("事件处理测试", () => {
  // 创建按钮
  const button = createToggleButton("切换我");

  // 初始状态
  assertEquals(button.dataset.active, "false");
  assertEquals(button.classList.contains("active"), false);

  // 模拟点击事件
  button.dispatchEvent(new Event("click"));

  // 第一次点击后的测试
  assertEquals(button.dataset.active, "true");
  assertEquals(button.classList.contains("active"), true);

  // 再次点击
  button.dispatchEvent(new Event("click"));

  // 第二次点击后的测试
  assertEquals(button.dataset.active, "false");
  assertEquals(button.classList.contains("active"), false);
});

测试 Fetch 请求 Jump to heading

测试会发起网络请求的组件需要对 fetch API 进行模拟(mock)。

下面的示例将 mock fetch API,以测试一个从外部 API 获取用户数据的函数。该测试创建了一个 spy 函数,它会根据请求的 URL 返回预定义的响应,使你无需发起真实网络调用,就能测试成功请求和错误处理:

import { assertSpyCalls, spy } from "jsr:@std/testing/mock";
import { assertEquals } from "jsr:@std/assert";

// 获取数据的组件
async function fetchUserData(
  userId: string,
): Promise<{ name: string; email: string }> {
  const response = await fetch(`https://api.example.com/users/${userId}`);
  if (!response.ok) {
    throw new Error(`获取用户失败:${response.status}`);
  }
  return await response.json();
}

Deno.test("fetch 请求测试", async () => {
  // 模拟 fetch 响应
  const originalFetch = globalThis.fetch;

  const mockFetch = spy(async (input: RequestInfo | URL): Promise<Response> => {
    const url = input.toString();
    if (url === "https://api.example.com/users/123") {
      return new Response(
        JSON.stringify({ name: "John Doe", email: "john@example.com" }),
        { status: 200, headers: { "Content-Type": "application/json" } },
      );
    }
    return new Response("未找到", { status: 404 });
  });

  // 用 mock 替换全局 fetch
  globalThis.fetch = mockFetch;

  try {
    // 使用有效 ID 调用函数
    const userData = await fetchUserData("123");

    // 断言结果
    assertEquals(userData, { name: "John Doe", email: "john@example.com" });
    assertSpyCalls(mockFetch, 1);

    // 测试错误处理(可选)
    try {
      await fetchUserData("invalid");
      throw new Error("对于无效 ID 应该抛出错误");
    } catch (error) {
      assertEquals((error as Error).message, "获取用户失败:404");
    }

    assertSpyCalls(mockFetch, 2);
  } finally {
    // 恢复原始 fetch
    globalThis.fetch = originalFetch;
  }
});

Using test steps for setup and teardown Jump to heading

For complex tests, you can use steps to organize test logic into discrete code blocks, making tests easier to read and maintain. Steps also provide better isolation between different parts of a test. By naming steps, you can initialize and clean up test conditions.

import { DOMParser } from "jsr:@b-fuze/deno-dom";
import { assertEquals, assertExists } from "jsr:@std/assert";

Deno.test("Complex Web Component Test", async (t) => {
  const doc = new DOMParser().parseFromString(
    "<!DOCTYPE html><html></html>",
    "text/html",
  );
  const body = doc.createElement("body");
  const container = doc.createElement("div");
  body.appendChild(container);

  await t.step("Initial render", () => {
    container.innerHTML = `<div id="app"></div>`;
    const app = container.querySelector("#app");
    assertExists(app);
    assertEquals(app.children.length, 0);
  });

  await t.step("Add content", () => {
    const app = container.querySelector("#app");
    assertExists(app);

    const header = doc.createElement("header");
    header.textContent = "My App";
    app.appendChild(header);

    assertEquals(app.children.length, 1);
    assertEquals(app.firstElementChild?.tagName.toLowerCase(), "header");
  });

  await t.step("Respond to user input", () => {
    const app = container.querySelector("#app");
    assertExists(app);

    const button = doc.createElement("button");
    button.textContent = "Click me";
    button.id = "test-button";
    app.appendChild(button);

    let clickCount = 0;
    button.addEventListener("click", () => clickCount++);

    button.dispatchEvent(new Event("click"));
    button.dispatchEvent(new Event("click"));

    assertEquals(clickCount, 2);
  });

  await t.step("Remove content", () => {
    const app = container.querySelector("#app");
    assertExists(app);

    const header = app.querySelector("header");
    assertExists(header);

    header.remove();
    assertEquals(app.children.length, 1); // Only the button should remain
  });
});

Best Practices for Web Testing in Deno Jump to heading

  1. Keep tests isolated — each test should be self-contained and not depend on other tests.

  2. Use names that reflect intent — give tests descriptive names that clearly indicate what is being tested and make console output easier to read.

  3. Clean up after tests — remove any DOM elements created during testing to prevent test pollution.

  4. Mock external services (such as APIs) to speed up tests and improve reliability.

  5. For complex components, use t.step() to organize tests into logical steps.

运行你的测试 Jump to heading

使用 Deno 的 test 命令执行测试:

›_
deno test

对于 Web 测试,你可能需要额外权限:

›_
deno test --allow-net --allow-read --allow-env

🦕 按照本教程中的模式,你可以为你的 Web 应用编写全面的测试,以验证功能和用户体验。

请记住,有效的测试能让应用更稳健,并帮助你在问题到达用户之前就将其发现。

Did you find what you needed?

编辑此页面
Privacy policy