如何用Jest+TypeScript模拟类属性并通过CommandFactory测试?
问题:如何正确模拟Discord.js的ChatInputCommandInteraction实例进行单元测试?
我是Jest和单元测试新手,用TypeScript开发,现在要测试CommandFactory类的getCommand方法。我尝试模拟Discord.js的ChatInputCommandInteraction类,给它的commandName属性设为'add-game',但测试代码因为类型不匹配报错,提示缺少大量必需属性。
待测试类(CommandFactory.ts)
import type {ChatInputCommandInteraction} from 'discord.js'; import type CommandInterface from './interfaces/CommandInterface'; import GameCommand from './commands/GameCommand'; import SettingCommand from './commands/SettingCommand'; import SetupCommand from './commands/SetupCommand'; import commands from '@config/commands'; export default class CommandFactory { public getCommand(interaction: ChatInputCommandInteraction): CommandInterface { for (const command of commands) { if (command.name === interaction.commandName) { if (command.classType === 'SettingCommand') { return new SettingCommand(interaction, command); } if (command.classType === 'GameCommand') { return new GameCommand(interaction, command); } if (command.classType === 'SetupCommand') { return new SetupCommand(interaction, command); } } } throw new Error(`Unexpected '${interaction.commandName}' command.`); } }
测试代码(CommandFactory.test.ts)
import CommandFactory from '@components/CommandFactory'; import type {ChatInputCommandInteraction} from 'discord.js'; import type {jest} from '@jest/globals'; import {test, expect} from '@jest/globals'; import GameCommand from '@components/commands/GameCommand'; const interaction: jest.MockedObject<ChatInputCommandInteraction> = { commandName: 'add-game', }; test('Instantiates GameCommand', () => { const factory = new CommandFactory(); const command = factory.getCommand(interaction); expect(command).toBeInstanceOf(GameCommand); });
报错信息
tests/components/commands/CommandFactory.test.ts:7:7 - error TS2322: Type '{ commandName: string; }' is not assignable to type 'MockedObject<ChatInputCommandInteraction<CacheType>>'. Type '{ commandName: string; }' is missing the following properties from type '{ commandType: ApplicationCommandType.ChatInput; options: MockedObject<Omit<CommandInteractionOptionResolver<CacheType>, "getMessage" | "getFocused">>; ... 50 more ...; valueOf: MockedFunction<...>; }': commandType, options, inGuild, inCachedGuild, and 46 more.
解决方案
ChatInputCommandInteraction是包含大量属性和方法的复杂接口,测试时不需要模拟所有属性,只需要提供getCommand方法用到的commandName即可。以下是几种实用的解决方式:
方法1:Partial结合类型断言
创建仅包含commandName的对象,通过类型断言告诉TypeScript它符合目标类型(测试中仅访问commandName,其他属性不会被调用):
import CommandFactory from '@components/CommandFactory'; import type {ChatInputCommandInteraction} from 'discord.js'; import {test, expect} from '@jest/globals'; import GameCommand from '@components/commands/GameCommand'; // 仅模拟需要的属性,通过双重断言绕过类型检查 const interaction = { commandName: 'add-game' } as Partial<ChatInputCommandInteraction> as ChatInputCommandInteraction; test('Instantiates GameCommand', () => { const factory = new CommandFactory(); const command = factory.getCommand(interaction); expect(command).toBeInstanceOf(GameCommand); });
方法2:Mocked包裹Partial类型
如果后续需要给对象添加模拟方法,可以用jest.Mocked结合Partial,既保留类型提示,又无需模拟全部属性:
import CommandFactory from '@components/CommandFactory'; import type {ChatInputCommandInteraction} from 'discord.js'; import type {jest} from '@jest/globals'; import {test, expect} from '@jest/globals'; import GameCommand from '@components/commands/GameCommand'; // 定义仅包含部分属性的模拟类型 type MockedInteraction = jest.Mocked<Partial<ChatInputCommandInteraction>>; const interaction: MockedInteraction = { commandName: 'add-game', // 后续需要模拟方法时可添加,比如: // reply: jest.fn() }; test('Instantiates GameCommand', () => { const factory = new CommandFactory(); // 断言为完整类型传入方法 const command = factory.getCommand(interaction as ChatInputCommandInteraction); expect(command).toBeInstanceOf(GameCommand); });
方法3:使用Discord.js官方测试工具(可选)
若需要更贴近真实场景的模拟,可使用Discord.js官方提供的@discordjs/testing库创建符合类型的交互对象:
import CommandFactory from '@components/CommandFactory'; import {test, expect} from '@jest/globals'; import GameCommand from '@components/commands/GameCommand'; import {createChatInputCommandInteraction} from '@discordjs/testing'; test('Instantiates GameCommand', () => { // 创建带指定commandName的模拟交互 const interaction = createChatInputCommandInteraction({ commandName: 'add-game' }); const factory = new CommandFactory(); const command = factory.getCommand(interaction); expect(command).toBeInstanceOf(GameCommand); });
使用前需先安装依赖:npm install @discordjs/testing --save-dev
内容的提问来源于stack exchange,提问作者Clearasil
相关产品推荐
相关产品推荐

