发布于 2026-01-06 4 阅读
0

揭秘 NestJS WebSocket 网关:有效测试的分步指南 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

揭秘 NestJS WebSocket 网关:高效测试的分步指南

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

介绍

在瞬息万变的实时应用领域,WebSocket 通信已成为在客户端和服务器之间建立无缝高效连接的强大工具。流行的 TypeScript 框架 NestJS 集成了 WebSocket 网关,简化了其生态系统内的实时通信。使用 NestJS 实现 WebSocket 功能固然令人兴奋,但确保其可靠高效运行同样至关重要。而这正是测试发挥关键作用的地方。

欢迎阅读这篇关于创建 NestJS WebSocket 网关测试的全面指南。在本文中,我们将深入探讨 WebSocket 网关的基础知识,并演示如何编写强大而有效的测试来验证其行为。

无论您是经验丰富的 NestJS 开发人员,希望扩展您的知识,还是渴望探索 WebSocket 网关的新手,本文都将为您提供有价值的见解和实用示例,帮助您开始掌握在 NestJS 应用程序中测试实时功能的艺术。

第一部分:设置 NestJS WebSocket 网关:分步指南

在本指南的第一部分,我们将引导您完成设置具有 WebSocket 功能的 NestJS 应用程序并生成聊天网关的过程。在本节结束时,您将拥有构建基于 WebSocket 的应用程序并为其编写测试的坚实基础。

步骤 1:安装 NestJS CLI

首先,请确保您的计算机上已安装 Node.js。

如果您尚未安装 NestJS 命令行界面 (CLI),可以通过运行以下命令进行全局安装:



npm i -g @nestjs/cli


Enter fullscreen mode Exit fullscreen mode

步骤 2:创建新的 Nest 应用程序

安装好 NestJS CLI 后,执行以下命令创建一个新的 Nest 应用程序:



nest new nest-app


Enter fullscreen mode Exit fullscreen mode

在安装过程中,系统会提示您选择包管理器。本教程将使用 npm,因此请在提示时选择它。

图片描述

图片描述

应用程序生成后,使用以下命令导航到项目目录:



cd nest-app


Enter fullscreen mode Exit fullscreen mode

步骤 3:安装所需软件包

为了向我们的应用程序添加 WebSocket 功能,我们需要安装必要的软件包。运行以下命令安装 @nestjs/websockets 和 @nestjs/platform-socket.io:



npm i --save @nestjs/websockets @nestjs/platform-socket.io


Enter fullscreen mode Exit fullscreen mode

步骤 4:生成聊天网关

安装好所需软件包后,我们来创建聊天网关。该网关将处理 WebSocket 连接并实现实时通信。使用 Nest CLI 生成聊天网关样板代码:



nest generate gateway chat


Enter fullscreen mode Exit fullscreen mode

图片描述

此命令将在 src/chat/ 目录下创建名为 chat.gateway.ts 和 chat.gateway.spec.ts 的新文件,您可以使用您选择的文本编辑器打开它们:



vim src/chat/chat.gateway.ts


Enter fullscreen mode Exit fullscreen mode

打开聊天网关文件后,你会看到网关的基本结构,其中包括带有 WebSocketGateway 装饰器的类定义。我们将在此结构的基础上,随着文章的推进添加更多功能。

恭喜!至此,您已成功为 NestJS 应用程序配置了 WebSocket 功能并生成了聊天网关。下一节,我们将深入探讨 WebSocket 网关,并开始编写测试以确保其功能正常。

第二部分:实现聊天网关并理解代码

在本节中,我们将用功能更丰富的聊天网关实现替换原有的样板代码。我们将逐一分析代码,了解其用途,并讨论我们所做的更改。

在深入代码之前,让我们先简要回顾一下网关中使用的每个接口和装饰器的用途:

  • @WebSocketGateway()此装饰器将类标记为 WebSocket 网关,使其能够处理 WebSocket 连接和事件。

  • @WebSocketServer()此装饰器将 WebSocket 服务器实例(Socket.io 实例)注入到网关中,从而实现与已连接客户端的直接通信。

  • OnGatewayInit此接口提供了一个afterInit()在 WebSocket 网关初始化时执行的方法。它是执行与网关初始化相关的任何设置或日志记录的理想位置。

  • OnGatewayConnection此接口提供了一个方法,该方法handleConnection(client: any, ...args: any[])会在建立新的 WebSocket 连接时被调用。您可以在此处处理与客户端连接相关的任务,例如记录日志或广播欢迎消息。

  • OnGatewayDisconnect此接口提供了一个方法handleDisconnect(client: any),该方法会在 WebSocket 客户端断开连接时触发。您可以使用此方法执行任何清理任务或记录断开连接事件。

  • @SubscribeMessage('ping')此装饰器表明该方法handleMessage(client: any, data: any)将处理客户端发送的事件名称为“ping”的消息。在本例中,它只是简单地记录接收到的消息,并返回一个名为“pong”的响应事件,其中包含固定的响应数据。

现在,让我们仔细看看我们对聊天网关所做的代码更改:



import { Logger } from "@nestjs/common";
import {
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnGatewayInit,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from "@nestjs/websockets";

import { Server } from "socket.io";

@WebSocketGateway()
export class ChatGateway
  implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect
{
  private readonly logger = new Logger(ChatGateway.name);

  @WebSocketServer() io: Server;

  afterInit() {
    this.logger.log("Initialized");
  }

  handleConnection(client: any, ...args: any[]) {
    const { sockets } = this.io.sockets;

    this.logger.log(`Client id: ${client.id} connected`);
    this.logger.debug(`Number of connected clients: ${sockets.size}`);
  }

  handleDisconnect(client: any) {
    this.logger.log(`Cliend id:${client.id} disconnected`);
  }

  @SubscribeMessage("ping")
  handleMessage(client: any, data: any) {
    this.logger.log(`Message received from client id: ${client.id}`);
    this.logger.debug(`Payload: ${data}`);
    return {
      event: "pong",
      data: "Wrong data that will make the test fail",
    };
  }
}


Enter fullscreen mode Exit fullscreen mode

在上面的代码中,我们用更函数式的实现替换了之前的样板网关:

  1. @nestjs/common我们从和导入了所需的模块和类@nestjs/websockets,以及Server来自 'socket.io' 的用于 WebSocket 通信的类。

  2. 我们定义了一个日志记录器,用于@nestjs/common在网关内部进行日志记录。该日志记录器在测试期间有助于监控事件。

  3. 在该afterInit()方法内部,我们会记录一条简单的消息,表明 WebSocket 网关已初始化。这有助于跟踪应用程序的生命周期。

  4. 在该handleConnection(client: any, ...args: any[])方法中,我们处理新的 WebSocket 连接。我们会记录客户端 ID 和已连接客户端的总数,以便跟踪活跃连接。

  5. handleDisconnect(client: any)方法负责记录断开连接事件。当 WebSocket 客户端断开连接时,将调用此方法执行任何必要的清理或日志记录操作。

  6. 我们实现了一个@SubscribeMessage('ping')名为 `.` 的方法handleMessage(client: any, data: any)。该方法处理来自已连接客户端的事件名称为“ping”的传入消息。它会记录接收到的消息及其有效负载,并返回一个名为“pong”的固定响应事件,其中包含一个故意包含错误消息的数据字段。我们添加此故意错误是为了演示如何在下一节中测试错误行为。

随着新的聊天网关部署到位,我们的 WebSocket 应用程序现在可以处理连接、断开连接以及来自客户端的传入消息。在本文的下一部分,我们将重点介绍如何为该网关编写测试,以确保其功能正常且稳定可靠。

第三部分:为 NestJS WebSocket 网关编写测试

在本指南的最后一部分,我们将探讨如何为之前创建的 ChatGateway 编写测试。在本节结束时,您将拥有一套测试用例,以确保您的 WebSocket 网关能够可靠​​且按预期运行。

让我们打开测试文件:



vim src/chat/chat.gateway.spec.ts


Enter fullscreen mode Exit fullscreen mode

并将样板代码替换为以下测试:



import { Test } from "@nestjs/testing";
import { ChatGateway } from "./chat.gateway";
import { INestApplication } from "@nestjs/common";
import { Socket, io } from "socket.io-client";

async function createNestApp(...gateways: any): Promise<INestApplication> {
  const testingModule = await Test.createTestingModule({
    providers: gateways,
  }).compile();
  return testingModule.createNestApplication();
}

describe("ChatGateway", () => {
  let gateway: ChatGateway;
  let app: INestApplication;
  let ioClient: Socket;

  beforeAll(async () => {
    // Instantiate the app
    app = await createNestApp(ChatGateway);
    // Get the gateway instance from the app instance
    gateway = app.get<ChatGateway>(ChatGateway);
    // Create a new client that will interact with the gateway
    ioClient = io("http://localhost:3000", {
      autoConnect: false,
      transports: ["websocket", "polling"],
    });

    app.listen(3000);
  });

  afterAll(async () => {
    await app.close();
  });

  it("should be defined", () => {
    expect(gateway).toBeDefined();
  });

  it('should emit "pong" on "ping"', async () => {
    ioClient.connect();
    ioClient.emit("ping", "Hello world!");
    await new Promise<void>((resolve) => {
      ioClient.on("connect", () => {
        console.log("connected");
      });
      ioClient.on("pong", (data) => {
        expect(data).toBe("Hello world!");
        resolve();
      });
    });
    ioClient.disconnect();
  });
});


Enter fullscreen mode Exit fullscreen mode

在这个测试文件中,我们使用该@nestjs/testing软件包设置一个测试模块,并将其编译以创建一个 NestJS 应用程序实例。该createNestApp()函数以 ChatGateway 作为参数,并返回已初始化的应用程序。

然后我们创建一个 Socket.io 客户端(ioClient),它连接到 WebSocket 服务器http://localhost:3000。该autoConnect: false选项确保客户端不会自动连接,并且我们将传输方式指定为“websocket”和“polling”,以便 websocket 连接成为默认的尝试连接选项。

现在,让我们逐一分析我们编写的两个测试用例:

  1. 第一个测试只是检查聊天网关是否已定义。我们期望网关在实例化后就被定义。这确保网关已正确设置并准备好处理 WebSocket 连接。

  2. 第二个测试模拟客户端向服务器发送的“ping”事件。我们预期服务器会响应一个包含与“ping”事件中发送的数据相同的“pong”事件。然而,当前的实现方式handleMessage()在响应中返回了错误的数据,导致测试失败。

要运行测试,首先需要安装 socket.io-client 软件包:



npm install -D socket.io-client


Enter fullscreen mode Exit fullscreen mode

注意:您无需安装 @types/socket.io-client 类型,因为它已经在主包中了。

然后在另一个 shell 窗口中使用以下命令:



npm run test:watch


Enter fullscreen mode Exit fullscreen mode

测试套件以监视模式运行,对 ChatGateway 或测试文件所做的任何更改都会触发测试自动重新运行,从而确保您的 WebSocket 网关在整个开发过程中保持健壮并经过适当的测试。

不出所料,其中一项测试失败了。

要修复网关handleMessage()方法中的错误,请按如下方式更新:



@SubscribeMessage("ping")
handleMessage(client: any, data: any) {
  this.logger.log(`Message received from client id: ${client.id}`);
  this.logger.debug(`Payload: ${data}`);
  return {
    event: "pong",
    data,
  };
}


Enter fullscreen mode Exit fullscreen mode

现在,只要实现正确,测试应该就能成功通过。

恭喜!您已成功设置并测试了 NestJS WebSocket 网关。掌握了如何测试 WebSocket 功能后,您可以自信地使用 NestJS 开发实时应用程序,因为您知道您的 WebSocket 通信是可靠且高效的。

感谢您与我一同探索使用 NestJS 实现 WebSocket 网关的旅程!希望本指南对您的实时应用程序开发工作有所帮助。

如果您喜欢这篇文章并觉得它对您有所帮助,请点赞或在下方留言。您的反馈和互动鼓励我创作更多类似的内容,帮助社区共同学习成长。

祝您编码愉快,愿您的 WebSocket 应用借助 NestJS 蓬勃发展!🚀🌟

文章来源:https://dev.to/jfrancai/demystifying-nestjs-websocket-gateways-a-step-by-step-guide-to- effective-testing-1a1f