deno.com
本页内容

测试 Web 应用

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

测试 UI 组件和 DOM 操作 跳转到标题

假设您有一个显示用户资料的网站,您可以设置一个测试函数来验证 DOM 元素创建是否正常工作。此代码设置了一个基本的卡片元素,然后测试所创建的 DOM 结构是否与预期匹配。

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

// Component or function that manipulates the 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 manipulation test", () => {
  // Create a test user
  const testUser = { name: "Test User", email: "test@example.com" };

  // Call the function
  const card = createUserCard(testUser);

  // Assert the DOM structure is correct
  assertEquals(card.className, "user-card");
  assertEquals(card.children.length, 2);
  assertEquals(card.querySelector("h2")?.textContent, "Test User");
  assertEquals(card.querySelector(".email")?.textContent, "test@example.com");
});

测试事件处理 跳转到标题

Web 应用程序通常通过事件处理用户交互。以下是如何测试事件处理程序。此代码设置了一个按钮,该按钮跟踪其活动/非活动状态并在点击时更新其外观。配套的测试通过创建一个按钮、检查其初始状态、模拟点击以及断言按钮在每次交互后正确更新其状态来验证切换功能。

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

// Component with event handling
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("event handling test", () => {
  // Create button
  const button = createToggleButton("Toggle Me");

  // Initial state
  assertEquals(button.dataset.active, "false");
  assertEquals(button.classList.contains("active"), false);

  // Simulate click event
  button.dispatchEvent(new Event("click"));

  // Test after first click
  assertEquals(button.dataset.active, "true");
  assertEquals(button.classList.contains("active"), true);

  // Simulate another click
  button.dispatchEvent(new Event("click"));

  // Test after second click
  assertEquals(button.dataset.active, "false");
  assertEquals(button.classList.contains("active"), false);
});

测试 Fetch 请求 跳转到标题

测试进行网络请求的组件需要模拟 fetch API。

在下面的示例中,我们将 模拟 fetch API 来测试一个从外部 API 获取用户数据的函数。该测试创建了一个间谍函数(spy function),根据请求的 URL 返回预定义响应,使您无需进行实际网络调用即可测试成功的请求和错误处理。

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

// Component that fetches data
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(`Failed to fetch user: ${response.status}`);
  }
  return await response.json();
}

Deno.test("fetch request test", async () => {
  // Mock fetch response
  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("Not found", { status: 404 });
  });

  // Replace global fetch with mock
  globalThis.fetch = mockFetch;

  try {
    // Call the function with a valid ID
    const userData = await fetchUserData("123");

    // Assert the results
    assertEquals(userData, { name: "John Doe", email: "john@example.com" });
    assertSpyCalls(mockFetch, 1);

    // Test error handling (optional)
    try {
      await fetchUserData("invalid");
      throw new Error("Should have thrown an error for invalid ID");
    } catch (error) {
      assertEquals((error as Error).message, "Failed to fetch user: 404");
    }

    assertSpyCalls(mockFetch, 2);
  } finally {
    // Restore the original fetch
    globalThis.fetch = originalFetch;
  }
});

使用测试步骤进行设置与清理 跳转到标题

对于复杂的测试,您可以使用步骤将测试逻辑组织成独立的部分,使测试更易读、更易维护。步骤还有助于在测试的不同部分之间实现更好的隔离。通过步骤命名,您可以实现测试条件的设置和清理。

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 rendering", () => {
    container.innerHTML = `<div id="app"></div>`;
    const app = container.querySelector("#app");
    assertExists(app);
    assertEquals(app.children.length, 0);
  });

  await t.step("adding 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("responding 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("removing 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
  });
});

Deno 中 Web 测试的最佳实践 跳转到标题

  1. 保持隔离性 - 每个测试都应自包含,且不依赖于其他测试。

  2. 使用名称来表明意图 - 为测试使用描述性名称可以清楚地表明正在测试什么,并在控制台中提供更易读的输出。

  3. 测试后清理 - 移除测试期间创建的任何 DOM 元素,以防止测试污染。

  4. 模拟外部服务(例如 API),使测试更快、更可靠。

  5. 对于复杂组件,使用 t.step() 将测试组织成逻辑步骤。

运行测试 跳转到标题

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

deno test

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

deno test --allow-net --allow-read --allow-env

🦕 通过遵循本教程中的模式,您可以为您的 Web 应用程序编写全面的测试,以验证功能和用户体验。

请记住,有效的测试可以带来更健壮的应用程序,并有助于在问题触达用户之前发现它们。

您找到所需内容了吗?

隐私政策