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
}
}
如何开发单位测试来涵盖儿童的ngoninit不嘲笑父母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基类功能。这两个测试成功通过了。现在,想象一下基础上的一些变化,例如"足球"常数是在基类及其单位测试中变为"网球"。在这种情况下,这两个类的单元测试将成功通过。
让我们拒绝基础阶级嘲笑的想法。我们将有:
describe('DesktopClass', () => {
const desktop: Desktop = new Desktop();
it('#setSport', () => {
desktop.setSport();
expect(desktop.isFootball).toBe(true);
});
});
在第一种情况下,两种测试都通过了,但是如果我们将"足球"更改为基类及其单位测试的"网球",那么台式机的测试将会失败。当大团队在一个大型项目上工作并在几个文件中进行更改,但忘记了其他问题,这是很普遍的错误。
我想引用埃里克·埃利奥特(Eric Elliott)的最后一篇文章"嘲笑是代码气味",尤其是一些引号:
什么是紧密的耦合?
子类耦合:子类取决于实现和父级的整个层次结构:最紧密的耦合形式在OO设计中可用。
是什么原因导致紧密耦合?
突变与不变性,副作用与纯度/孤立的副作用等
保持基类调用在某个角度违反了单位测试的术语,并且可能需要更多的基础服务服务模拟。我们需要在单独的文件中移动这些模拟以保持干燥。想一想要选择的是两倍:更快的简单代码或针对错误的额外保险。