【问题标题】:Angular 2 TestBed, Mocking Methods without Dependency InjectionAngular 2 TestBed,没有依赖注入的模拟方法
【发布时间】:2017-01-22 06:09:01
【问题描述】:

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

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

如果有人能建议一种方法来创建模拟类来覆盖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();
    }));
});

【问题讨论】:

    标签: unit-testing angular typescript jasmine angular-rc5


    【解决方案1】:

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

    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,只需将方法设为非静态方法,对于第三方库,只需为其创建抽象服务层即可。

    【讨论】:

    • 我只是在beforeEach() 中覆盖了ScriptLoader.load 并工作了。谢谢。但是,您对beforeEachafterEach 所做的是每个it 每次都会覆盖ScriptLoader.load 并将其带回一个空变量,对吗?那有什么意义呢?或许,我没有区分覆盖静态方法和将新函数分配给函数的区别?
    • 只是为了重置它,以便您可以根据需要在每个测试中使用不同的方法。如果您希望所有测试都相同,可以使用beforeAllafterAll。请记住,静态方法保留在类中,而不是实例中。因此,在此测试中更改它甚至会影响使用它的其他测试(文件)。所以我们想在完成测试之前把它重置回原来的方法
    • " 我没有区分覆盖静态方法和将新函数分配给函数之间的区别?" - 不知道你所说的覆盖是什么意思。我认为您所指的是同一件事
    • 将新函数分配给静态方法如何影响其他测试(文件)?我认为它可以在单个测试的范围内被覆盖。也就是说,其他测试(文件)不会看到被覆盖的方法。他们只会引用完整的原始静态方法。我错了吗?
    • 静态方法是“类作用域”。这意味着它在课堂上保持不变。如果你改变它,只要那个类还活着,它就会保持改变。不管它被导入到什么文件中。你可以测试一下。获取两个不同的测试文件并更改 a 中的静态变量(在执行的第一个文件中),您应该在运行的第二个文件中看到值保持更改。这与“实例”变量的工作方式不同,每个实例都有自己的值
    猜你喜欢
    • 2016-12-19
    • 1970-01-01
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 2020-06-06
    • 2020-02-13
    • 1970-01-01
    相关资源
    最近更新 更多