javascript - 如何使用 Jasmine 在单元测试中模拟 super.ngOnInit() ?如何在基类/父类方法上创建 spy 以仅覆盖派生/子类代码?

标签 javascript angular unit-testing inheritance jasmine

export class Parent implements OnInit {
    ngOnInit(): void {
    // huge amount of different services calls
    }
}

export class Child extends Parent implements OnInit {
    ngOnInit(): void {
        super.ngOnInit();
        // a few more functions
    }
}  

如何开发单元测试来覆盖 Child 的 ngOnInit 而不模拟 Parent ngOnInit 的所有服务功能?

我的尝试是这样的:

let child: Child;
const mockParent = {
    ngOnInit: jasmine.createSpy('ngOnInit')
};
child = new Child();  // base object is created already
Object.getPrototypeOf(child) = jasmine.createSpy('Parent').and.callFake(() => mockParent);  // so this doesn't work

最佳答案

有一个解决方案如何监视父类函数。

Parent.prototype.ngOnInit = jasmine.createSpy('ngOnInit');

但是该解决方案不够安全。让我们看一下示例:

class Mobile {
    sport: string;
  
    setSport(): void {
        this.sport = 'Football';
    }
}

describe('MobileClass', () => {
    const mobile: Mobile = new Mobile();
  
    it('#setSport', () => {
        mobile.setSport();
        expect(mobile.sport).toBe('Football');
    });
});

class Desktop extends Mobile {
    isFootball: boolean;
  
    setSport(): void {
        super.setSport();
        this.isFootball = this.func(this.sport);
    }
  
    func(sp: string): boolean {
        return sp === 'Football' ? true : false;
    }
}

describe('DesktopClass', () => {
    const desktop: Desktop = new Desktop();
  
    it('#setSport', () => {
        Mobile.prototype.setSport = jasmine.createSpy('setSport');
        desktop.sport = 'Basketball';
  
        desktop.setSport();
        expect(Mobile.prototype.setSport).toHaveBeenCalled();
        expect(desktop.isFootball).toBe(false);
    });

    it('#func', () => { 
        // 2 cases covered
        ...
    });
});

上面我们窥探了 setSport 基类函数。两项测试均顺利通过。 现在想象一下在基类中进行了一些更改,例如在基类及其单元测试中,“Football”常量更改为“Tennis”。在这种情况下,两个类的单元测试都将成功通过。

让我们拒绝基类模拟的想法。我们将有:

describe('DesktopClass', () => {
    const desktop: Desktop = new Desktop();
  
    it('#setSport', () => {
        desktop.setSport();
        expect(desktop.isFootball).toBe(true);
    });
});

在第一种情况下,两个测试都通过了,但是如果我们在基类及其单元测试中将“足球”更改为“网球”,那么现在,桌面测试将失败。 当大团队处理一个大型项目并在几个文件中进行更改但忘记其他文件时,这是很常见的错误,因为两个文件的单元测试都成功通过了。

最后我想引用这篇文章'Mocking is a code smell' by Eric Elliott特别是一些引言:

What is tight coupling?

Subclass coupling: Subclasses are dependent on the implementation and entire hierarchy of the parent class: the tightest form of coupling available in OO design.

What causes tight coupling?

Mutation vs immutability, Side-Effects vs purity/isolated side-effects, etc.

从某些 Angular 来看,保留基类调用违反了单元测试术语,并且可能需要对基类中使用的服务进行更多模拟。我们需要将这些模拟移动到单独的文件中以保持干燥。三思而后行,选择什么:更快、更简单的代码,还是针对错误的额外保险。

关于javascript - 如何使用 Jasmine 在单元测试中模拟 super.ngOnInit() ?如何在基类/父类方法上创建 spy 以仅覆盖派生/子类代码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55005399/

相关文章:

java - 如何使用 AssertThat 检查列表中对象的属性值?

javascript - 本地主机上的动态 facebook like 按钮

javascript - EmberJs Handlebars 预编译吗?

javascript - 在 Node 中本地使用 dynamodb 时出现 "Could not load credentials from any providers"

javascript - 具有一流功能的语言是否一定允许闭包?

angular - 错误 TS2307 : Cannot find module '@angular/core'

c# - NSubstitute 为对象返回 Null

angular - flask-cors 问题 - 被 CORS 策略 : Response to preflight request doesn't pass access control check: It does not have HTTP ok status 阻止

angular - 如何实现 Angular 的 "banana in a box"与自定义元素的双向绑定(bind)?

Python 单元测试 : having an external function for repetitive code in different modules