首页 > 解决方案 > 我如何开玩笑地模拟一个类的依赖关系

问题描述

我将如何模拟我正在用 jest 测试的类之一的依赖关系?

我有一个使用pigpio模块的类

// a trimmed down version of the class and method I'm writing a test for

import { Gpio } from "pigpio"

export default class BotController {

    private initializePenActuator(): void {
        this.penPause = Number(process.env.PEN_PAUSE_DELAY_IN_MILLISECONDS)
        this.penActuator = new Gpio(Number(process.env.PEN_ACTUATOR_PIN))
    }

    async setPenDirection(direction: penDirection, toggleAfterDelay?: boolean): Promise<void> {

        return new Promise((resolve, reject) => {
            this.penActuator.digitalWrite(direction) // the call I want to mock

            if (!toggleAfterDelay) {
                resolve()
            }

            setTimeout(() => {
                resolve()
            }, this.penPause)
        })
    }
}

我正在尝试为它编写以下测试:

    test("can actuate with a delay", async () => {
        // * actuate pen with a delay
        const start: Date = new Date()

        await controller.setPenDirection(penDirection.UP, true)

        const end: Date = new Date()
        const duration = end.getTime() - start.getTime()

        // * confirm that the async call was delayed for the correct number of MS
        expect(duration).toBeGreaterThanOrEqual(Number(process.env.PEN_PAUSE_DELAY_IN_MILLISECONDS))
    })

但我不确定如何为 pigpio 方法调用编写模拟digitalWrite

我知道我可以创建一个类的模拟:

jest.mock("pigpio", () => {
    return class {
        digitalWrite(value) {}
    }
})

但这会假设我在测试中直接调用 pigpio 类,而不是通过不同的类(对吗?)。

我一直在尝试阅读有关如何正确执行此操作的文档,但我似乎无法从示例中解析解决方案。如果不是很明显,我仍然对测试很满意。

我将如何模拟 pigpio 以便我可以测试我的班级的方法?

此外,如果您有指向我应该查看的文档中的位置的链接,我将不胜感激。我不知道我是否误解了我阅读的文档,或者我只是没有在开玩笑的文档中找到正确的位置:|

更新

在查看了 Estus 指出的文档中的自动模拟示例后,我看到了如何模拟该类的示例,如果该类是模块的默认导出。

https://jestjs.io/docs/en/es6-class-mocks#automatic-mock

但是,如果该类是模块的默认导出,则此示例有效。在我的情况下,该类不是默认导出:

要模拟的类不是默认导出

如果我尝试只模拟模块,我会收到一个错误:

尝试模拟 pigpio 模块时出错

如果我尝试为模块模拟 Gpio 类,我仍然会收到错误消息:

仅模拟 gpio 类,仍然出错

而且我似乎无法在文档中找到正确执行此操作的示例:/

另一个更新

我还尝试为Gpio该类创建一个模拟函数pigpio,模拟模块并为该模拟提供一个使用模拟的实现,Gpio但它仍然不起作用:

在此处输入图像描述

标签: typescriptunit-testingmockingjestjs

解决方案


太棒了,

我决定走依赖注入路线。我真的很想弄清楚如何在不走 DI 路线的情况下模拟 Gpio 类,但现在我只想继续前进。

类构造函数的依赖注入

那么,依赖注入应该相对容易吧?好吧,我试图做的依赖注入有一些障碍。我需要将该Gpio用作构造函数,而不是作为 Gpio 的实例。

在我BotController的课堂上,我使用Gpio该类来构造不同的实例Gpio

必须有一个构造函数

但是对于打字稿,如果你将一个类注入到构造函数中(我假设是方法),你不会得到类构造函数,你会得到一个类的实例。要注入构造函数而不是实例,您需要使用typeof

存储注入的构造函数

因为根据文档

这里我们使用 typeof Greeter,即“给我 Greeter 类本身的类型”而不是实例类型。或者,更准确地说,“给我一个叫做 Greeter 的符号的类型”,它是构造函数的类型。

所以现在,我可以模拟pigpio模块并将模拟的 Gpio 类传入以用作构造函数,并且测试不会崩溃。

在此处输入图像描述

明天,继续前进!!!!

更新

你有没有爬到山顶,疲惫而胜利地站在山顶,然后低头看到对面的自动扶梯?

发布此答案并上床睡觉后,我醒来后发现了 Estus 的另一条超级有用的评论:

你错过了 jest.mock 的 __esModule: true ,你需要它来命名 export 。没有它,一个 mock 被视为 CommonJS 模块,它转换为 ESM 默认导出。请参阅 jestjs.io/docs/en/...</p>

我在文档中没有看到任何关于此的内容,但是当您查看API 参考的给定部分时

掌心

因此,有了这些新知识,我能够返回并将所有 BotController 代码更改回其原始状态,从而消除依赖注入:

构造函数恢复正常

执行器代码恢复正常

在我的测试中,我将__esModule: true设置添加回了模拟的实现,用模拟传回了我的Gpio类模拟digitalWrite,我的测试通过了:

测试通过!

我觉得我已经经历了敲门声,但最后我很高兴知道解决这个问题的几种方法。希望这个问题和答案中的细节可以避免其他人的头痛。


推荐阅读