首页 > 解决方案 > Angular 7:在 UnitTest 中模拟 FileSaver.saveAs

问题描述

我有一个角度组件,其中包含以下部分:

my.component.html(摘录)

<button pButton
    class="download ui-button ui-button-secondary"
    (click)="exportFile(logEvent)"
    icon="fas fa-file-download">
</button>

my.component.ts(摘录)

import {saveAs} from 'file-saver';

exportFile(logEvent: LogEvent) {
    saveAs(new Blob([logEvent.details]), 'log-details.txt');
}

这在我的应用程序中完美运行。我现在想在我的单元测试中对此进行测试。在寻找一种确保已调用 saveAs() 的方法时,我偶然发现了两篇 stackoverflow 文章:mocking - Testing FileSaver in Angular 5Do you need spies to test if a function has been called in Jasmine? . 基于此,我编写了以下测试:

my.component.spec.ts(摘录)

import * as FileSaver from 'file-saver';

beforeEach(() => {
    spyOn(FileSaver, 'saveAs').and.stub();
});

it('should download a file if the download button is clicked', (fakeAsync() => {
  // fakeAsync because in my real test, there are httpClient test aspects as well
  advance(fixture);

  expect(page.downloadButton).toBeDefined();
  click(page.downloadButton);
  advance(fixture);

  expect(FileSaver.saveAs).toHaveBeenCalled();
}));

这两个辅助方法来自Angular 测试示例

export function advance(f: ComponentFixture<any>): void {
  tick();
  f.detectChanges();
}

export const ButtonClickEvents = {
  left: {button: 0},
  right: {button: 2}
};

export function click(el: DebugElement | HTMLElement, eventObj: any = ButtonClickEvents.left): void {
  if (el instanceof HTMLElement) {
    el.click();
  } else {
    el.triggerEventHandler('click', eventObj);
  }
}

我的问题是,该测试失败,输出如下:

错误:预期的间谍 saveAs 已被调用。

错误:1 个计时器仍在队列中。

所以看起来存根和断言似乎都不起作用。

如果我click()从我的 testm 中删除调用,则1 timer(s) still in the queue错误不再显示,所以我认为 click() 方法有效并触发了真正的saveAs()调用——我想用 spy/mock 替换它。

我怎样才能解决这个问题?


更新,考虑到建议的解决方案:

I. 按照 SiddarthPal 的建议将 my.component.spec.ts 中的导入更改为:

import * as FileSaver from 'file-saver';

没有任何区别。测试仍然导致两个断言错误。

二、按照 uminder 的建议将间谍设置更改为:

spyOn(FileSaver, 'saveAs').and.callFake(() => null);

也没有什么区别。测试仍然导致两个失败的断言。

我尝试编写间谍模拟如下:

spyOn(FileSaver, 'saveAs').and.callFake(() => {
    console.log('--- faking saveAs ---');
    return null;
});

检查 Karma Server 输出的输出,我在任何地方都看不到这个,所以看起来间谍根本没有捕捉到我的组件对 saveAs() 的调用。

三、uminder 建议更换:

click(page.downloadButton);
advance(fixture);

click(page.downloadButton);
flush();

确实消耗了挂起的计时器错误。但是,那个 onyl 隐藏了使用真实saveAs()调用而不是间谍/模拟的事实。因此,我仍在寻找使其发挥作用的方法。

标签: angularkarma-jasmine

解决方案


好吧,所以我现在正在努力解决完全相同的问题。我的间谍也没有工作,它调用了真正的 FileSaver 方法。这里的建议也没有为我解决问题。

我相信问题是, FileSaver 不是正确的 Angular 模块的一部分,它只是从FileSaver.js导入的。我在这个线程中描述了一个非常相似的模拟问题。Tl; dr:我通过从import使用 my切换Funnel到声明 aFunnelProvider并将其注入到我的真实方法的构造函数中来解决了这个问题,例如:

constructor(private funnelProvider: FunnelProvider) {}

然后,在规范文件中,我能够模拟该提供程序。

所以我想出了这两个解决方案:

1. 使用angular-file-saver

我想,好吧,也许有一个 FileSaver.js 包装器库为我提供了一个 Angular 服务,并且. 然而,依赖关系已经过时......这可能是一个问题。但是从 FileSaver.js 切换到 angular-file-saver 应该是一个简单的改变。

2. 为 FileSaver.js 构建自己的包装器

我现在能想到的唯一其他解决方案是自己将 FileSaver 使用包装到它自己的 Angular 模块中。然后导入该模块并将您新建的 FileSaverService 注入到您的真实类中。在规范文件中,您可以执行以下操作:

describe("MyComponent", () => {
    
    let component: MyComponent;
    let  fixture: ComponentFixture<MyComponent>;

    let fileSaverServiceSpy: jasmine.SpyObj<FileSaverService>;
   
    beforeEach(() => {
        fileSaverServiceSpy= jasmine.createSpyObj('FileSaverService', ['saveAs']);

        TestBed
        .configureTestingModule({
            declarations: [
                MyComponent
            ],
            providers: [
                { provide: FileSaverService, useValue: fileSaverServiceSpy }
            ]
        })
        .compileComponents()
        .then(() => {
            fixture = TestBed.createComponent(MyComponent);
            component = fixture.componentInstance;
        });
    });

    it('saveMyFile() should call FileSaver', () => {
        fileSaverServiceSpy.saveAs.and.stub();

        MyComponent.saveMyFile("testFileName");

        expect(fileSaverServiceSpy.saveAs).toHaveBeenCalled(); 
    });

});

然而,为了让间谍工作,所有这一切似乎都需要做很多工作。

编辑:我正在一个 Angular 9 项目上对此进行测试,这些是我的相关依赖项:

"@angular/core": "9.1.12",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "5.2.3",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-firefox-launcher": "^2.0.0",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"karma-typescript": "5.2.0",

推荐阅读