首页 > 解决方案 > 如何在我试图开玩笑测试的类中模拟私有属性

问题描述

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

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),
            },
        }
    }

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

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 指出如何在演员版本上调用该方法之后,错误在后门消失了,这太棒了!

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

更近

标签: javascripttypescriptunit-testingjestjs

解决方案


除非使用#硬隐私,否则私有属性可以在运行时在类外部访问,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();

推荐阅读