【问题标题】:How can I mock a private property in a class I'm trying to test in jest如何在我试图开玩笑测试的类中模拟私有属性
【发布时间】:2020-12-14 16:15:35
【问题描述】:

我有一个要测试的类方法:

setStepResolution(resolution: stepResolution): void {
        switch (resolution) {
            case stepResolution.FULL_SETUP:
                this.stepperMotors.left.ms1Pin.digitalWrite(0)
                this.stepperMotors.left.ms2Pin.digitalWrite(0)
                this.stepperMotors.left.ms3Pin.digitalWrite(1)
                this.stepperMotors.right.ms1Pin.digitalWrite(0)
                this.stepperMotors.right.ms2Pin.digitalWrite(0)
                this.stepperMotors.right.ms3Pin.digitalWrite(1)
                break
            case stepResolution.HALF_STEP:
                this.stepperMotors.left.ms1Pin.digitalWrite(1)
                this.stepperMotors.left.ms2Pin.digitalWrite(0)
                this.stepperMotors.left.ms3Pin.digitalWrite(0)
                this.stepperMotors.right.ms1Pin.digitalWrite(1)
                this.stepperMotors.right.ms2Pin.digitalWrite(0)
                this.stepperMotors.right.ms3Pin.digitalWrite(0)
                break

每个digitalWrite 调用都是针对构建我的类时创建的不同类的实例进行的:

export default class BotController {

    private stepperMotors: StepperMotorCollection

    constructor() {
        this.initalizeMotors()
    }

    private initalizeMotors(): void {
        this.stepperMotors = {
            left: {
                directionPin: new Gpio(Number(process.env.LEFT_DIRECTION_PIN), { mode: Gpio.OUTPUT }),
                stepPin: new Gpio(Number(process.env.LEFT_STEP_PIN), { mode: Gpio.OUTPUT }),
                ms1Pin: new Gpio(Number(process.env.LEFT_RESOLUTION_PIN_MS1), { mode: Gpio.OUTPUT }),
                ms2Pin: new Gpio(Number(process.env.LEFT_RESOLUTION_PIN_MS2), { mode: Gpio.OUTPUT }),
                ms3Pin: new Gpio(Number(process.env.LEFT_RESOLUTION_PIN_MS3), { mode: Gpio.OUTPUT }),
                stepsPerMM: Number(process.env.LEFT_STEPS_PER_MM),
                swapCoils: Boolean(process.env.LEFT_SWAP_COILS),
            },
            right: {
                directionPin: new Gpio(Number(process.env.RIGHT_DIRECTION_PIN), { mode: Gpio.OUTPUT }),
                stepPin: new Gpio(Number(process.env.RIGHT_STEP_PIN), { mode: Gpio.OUTPUT }),
                ms1Pin: new Gpio(Number(process.env.RIGHT_RESOLUTION_PIN_MS1), { mode: Gpio.OUTPUT }),
                ms2Pin: new Gpio(Number(process.env.RIGHT_RESOLUTION_PIN_MS2), { mode: Gpio.OUTPUT }),
                ms3Pin: new Gpio(Number(process.env.RIGHT_RESOLUTION_PIN_MS3), { mode: Gpio.OUTPUT }),
                stepsPerMM: Number(process.env.RIGHT_STEPS_PER_MM),
                swapCoils: Boolean(process.env.RIGHT_SWAP_COILS),
            },
        }
    }

我可以在我的测试中使用 Gpio 类的模拟来为 stepperMotors 属性创建一个模拟(我已经在模拟其他一些测试的构造函数):

test("can change step resolution", () => {
        // * The step resolution of the stepper motors can be changed via the code.
        // * The settings can be controlled by an enum that denotes each of the possible
        // * resolutions.

        const mockStepperMotorConfiguration: StepperMotorCollection = {
            left: {
                directionPin: new pigpio.Gpio(1),
                stepPin: new pigpio.Gpio(1),
                ms1Pin: new pigpio.Gpio(1),
                ms2Pin: new pigpio.Gpio(1),
                ms3Pin: new pigpio.Gpio(1),
                stepsPerMM: 1,
                swapCoils: false,
            },
            right: {
                directionPin: new pigpio.Gpio(1),
                stepPin: new pigpio.Gpio(1),
                ms1Pin: new pigpio.Gpio(1),
                ms2Pin: new pigpio.Gpio(1),
                ms3Pin: new pigpio.Gpio(1),
                stepsPerMM: 1,
                swapCoils: false,
            },
        }

        // ^ To change the resolution to a full step
        // * send in the full step enum
        newController.setStepResolution(stepResolution.FULL_SETUP)

但我不能,因为 stepperMotor 属性是私有的。

有几种方法可以解决这个问题(使属性公开,设置属性的公开方法),但它们似乎都不理想,因为该属性永远不能在类之外访问,所以我会公开属性或仅支持测试的方法。

还有其他方法可以进行这种测试吗?我开玩笑地知道我可以通过替换原型函数来模拟 javascript 中类的方法,例如:

BotController.prototype.someMethod = jest.fn()
const controller = new BotController

如果这是我试图模拟的类,我可以将属性作为模拟实现传递,例如:

jest.mock("../BotController", () => ({
    stepperMotors: mockStepperMotorConfiguration
}))

但是,课堂上的其他所有内容也会被嘲笑,你就会失去重点。

知道我应该如何处理这个问题吗?

更新:尝试创建后门

我正在尝试 Taplar 创建后门的方法。

我尝试将我的控制器实例转换为any

但是编译器还在骂我:

另一个更新

在 Taplar 指出如何在演员版本上调用该方法后,错误在后门消失了,这太棒了!

我撞到的下一堵墙是,由于某种原因,现在测试无法再看到我的模拟了,这很奇怪,因为该变量是测试的本地变量。

【问题讨论】:

  • jest 有能力用假方法模拟方法吗?或者即使没有,您也可以尝试替换类实例上的方法(或添加一个),然后尝试更改 this.stepperMotors
  • 天哪,我喜欢这种方法,那是超级切肉刀!可悲的是它不起作用,因为我使用的是打字稿。我试过了,编译器出错了,因为类中不存在后门属性:|使用这种方法,我也尝试替换原型上的 initalizeMotors 方法,但编译器也失败了,因为该方法也是私有的。
  • (yourInstance as any).backdoor = ...
  • 嗯,尝试了强制转换,但编译器仍在抱怨:|我喜欢在大多数情况下都有打字稿编译器,但在这里非常令人沮丧

标签: javascript typescript unit-testing jestjs


【解决方案1】:

除非使用# hard privacy,否则私有属性可以在运行时在类外部访问,TypeScript 访问修饰符仅在编译时应用。

在测试中访问私有成员可以被认为是一种反射。

可见性可以用括号表示法绕过,这是更可取的选择:

controllerInstance['stepperMotors'] = ...;

或使用反射 API:

Reflect.set(controllerInstance, 'stepperMotors', ...);

或者通过禁用类型检查:

(controllerInstance as any).stepperMotors = ...;

由于私有属性是用原型方法设置的,另一种方法是模拟它。如果原始初始化导致不良副作用并且需要避免,则适用。 BotController.prototype.someMethod = jest.fn() 永远不应该在 Jest 中使用,因为它不能自动清理并交叉污染测试。相反,它可能是:

jest.spyOn(BotController.prototype, 'initalizeMotors').mockImplementation(function (this: BotController) {
  this['stepperMotors'] = ...;
});
...
expect(controllerInstance['initalizeMotors']).toHaveBeenCalled();

【讨论】:

  • :facepalm: 天哪,我什至没想过要尝试括号表示法!读完这篇文章后,我意识到我在第一次引入 es6 类时读到过这样做,后来又忘记了。不过,我不知道 Reflect 类,这真是太好了!感谢您列出所有这些选项。有这些方法的例子真是太好了:bows:
猜你喜欢
  • 1970-01-01
  • 2021-06-16
  • 2021-08-15
  • 2022-01-21
  • 2017-11-19
  • 1970-01-01
  • 1970-01-01
  • 2018-09-18
  • 2019-10-26
相关资源
最近更新 更多