如何用Jest测试含第三方库只读字段(如ws的readyState)的代码
测试含第三方库只读字段的WebSocket代码解决方案
问题场景
我需要用Jest测试以下基于ws库的WebSocket连接代码,核心是验证重复连接时是否抛出指定错误。但测试过程中发现WebSocket实例的readyState是只读属性,无法直接修改,导致测试无法推进。
待测试代码(Socket.js)
import WebSocket from "ws"; export default class Socket { socket = undefined; connect(url): void { if (this.socket !== undefined && this.socket.readyState === WebSocket.OPEN) { throw new Error("Socket is already connected"); } this.socket = new WebSocket(url); } }
测试代码及问题
我尝试Mock ws库,但修改readyState时遇到TypeScript报错:无法赋值给'readyState',因为它是只读属性。ts(2540)
测试代码如下:
import Socket from "./Socket"; import WebSocket from "ws"; jest.mock("ws"); it("can connect", () => { // Arrange const url = "www.someurl.com"; jest.spyOn(global, "Promise").mockReturnValueOnce({} as Promise<void>); const sut = new Socket(); // Act sut.connect(url); const webSocket = jest.mocked(WebSocket).mock.instances[0]; // Set readystate of socket webSocket.readyState = WebSocket.CLOSED; // 这里报错:readyState是只读属性 const action = () => sut.connect(url); // Assert expect(action).toThrowError(new Error("Socket is already connected")); });
解决方案
针对只读属性的测试需求,有三种可行的处理方式:
方式1:在Mock实例初始化时直接定义readyState
修改Jest对ws的Mock逻辑,让创建实例时直接带上指定的readyState值:
import Socket from "./Socket"; import WebSocket from "ws"; jest.mock("ws", () => { // 自定义Mock构造函数,初始化时就设置readyState return jest.fn().mockImplementation(() => ({ readyState: WebSocket.OPEN // 直接设置为OPEN状态,满足触发错误的条件 })); }); it("throws error when connecting twice with open socket", () => { const url = "www.someurl.com"; const sut = new Socket(); // 第一次连接,创建mock实例(readyState为OPEN) sut.connect(url); // 第二次连接应该抛出错误 expect(() => sut.connect(url)).toThrowError("Socket is already connected"); });
方式2:用Object.defineProperty修改属性的可写性
如果需要在测试过程中动态修改readyState,可以用Object.defineProperty重新定义该属性,开启可写权限:
import Socket from "./Socket"; import WebSocket from "ws"; jest.mock("ws"); it("throws error when connecting twice with open socket", () => { const url = "www.someurl.com"; const sut = new Socket(); sut.connect(url); const webSocket = jest.mocked(WebSocket).mock.instances[0]; // 重新定义readyState为可写属性,并设置为OPEN Object.defineProperty(webSocket, "readyState", { value: WebSocket.OPEN, writable: true // 允许后续修改(如果需要) }); expect(() => sut.connect(url)).toThrowError("Socket is already connected"); });
方式3:模拟readyState的getter方法
通过jest.spyOn模拟readyState的getter,动态返回需要的值:
import Socket from "./Socket"; import WebSocket from "ws"; jest.mock("ws"); it("throws error when connecting twice with open socket", () => { const url = "www.someurl.com"; const sut = new Socket(); sut.connect(url); const webSocket = jest.mocked(WebSocket).mock.instances[0]; // 模拟readyState的getter,返回OPEN状态 jest.spyOn(webSocket, "readyState", "get").mockReturnValue(WebSocket.OPEN); expect(() => sut.connect(url)).toThrowError("Socket is already connected"); });
说明
三种方式中,方式1最简洁,适合只需要固定状态的测试场景;方式2和3更灵活,适合需要在测试过程中动态切换状态的场景,可根据测试需求选择。
内容的提问来源于stack exchange,提问作者Jupiter
相关产品推荐
相关产品推荐

