Angular 2 TestBed,没有依赖注入的模拟方法

Angular 2 TestBed, Mocking Methods without Dependency Injection

使用TestBed,我们能够为类创建可用于依赖注入的模拟类。例如,MyButtonClass 可以访问 ElementRefMyService,因为它们是通过依赖注入实现的,因此我们可以覆盖它们。我遇到的问题是,要编写 Jasmine 测试,我必须创建模拟 类 来覆盖 类 的方法,这些方法无法通过依赖注入访问。

在这种情况下,ScriptLoader.load 将在全局 space 中加载 ThirdPartyCheckout。这意味着,当 Jasmine 读取订阅操作符中的内容时,它可能不可用。出于这个原因,我想先嘲笑前者,然后再嘲笑后者。或者也许有不同的方法来解决这个问题。

如果有人可以建议一种创建模拟 类 以覆盖 ScriptLoader.load 方法和 ThirdPartyCheckout.configure 方法的方法,那就太好了。

要测试的指令:

@Directive({
    selector: '[myButton]'
})
export class MyButtonClass implements AfterViewInit {

    private myKey: string;

    constructor(private _el: ElementRef, private myService: MyService) {}

    ngAfterViewInit() {
        this.myService.getKey()
            .then((myKey: string) => {
                this.myKey = myKey;
                ScriptLoader.load('https://thirdpartyurl.js', this._el.nativeElement)
                    .subscribe(
                        data => {
                            this.handeler = ThirdPartyCheckout.configure(<any>{
                                key: this.myKey
                                // etc
                                // and some methods go here
                            });
                        },
                        error => {
                            console.log(error);
                        }
                    );
        });
    }
}

这里是测试代码:

@Component({
    selector: 'test-cmp',
    template: ''
})
class TestComponent {}

class mockMyService {
    getKey() {
        return Promise.resolve('this is a key in real code');
    }
}

describe('myButton', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [TestComponent, MyButtonClass],
            providers: [
                {provide: MyService, useClass: mockMyService}
            ]
        });
    });

    describe('ngAfterViewInit', fakeAsync(() => {
        const template = '<div><div myButton></div></div>';
        TestBed.overrideComponent(TestComponent, {set: {template: template}});
        let fixture = TestBed.createComponent(TestComponent);
        fixture.detectChanges();
        tick();
    }));
});

函数是第一个-class公民,你可以给它分配一个新函数

let originalFn;

beforeEach(() => {
  originalFn = ScriptLoader.load;
});

afterEach(() => {
  ScriptLoader.load = originalFn;
});

it('...', fakeAsync(() => {
  ScriptLoader.load = (url, el: Element): Observable<string> => {
    return Observable.of('HelloSquirrel');
  };
  ...
}));

除此之外,您可能只想考虑使用 DI。使用 DI 的主要原因之一是为了更好的可测试性。对于ScriptLoader只需将方法设为非静态方法,对于第三方库只需为其创建抽象服务层即可。