angular - 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 5和Do 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()
调用而不是间谍/模拟的事实。因此,我仍在寻找使其发挥作用的方法。
解决方案
好吧,所以我现在正在努力解决完全相同的问题。我的间谍也没有工作,它调用了真正的 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",
推荐阅读
- arrays - 两个字符数组之间的 CAPL 检查
- sql - 与 Oracle 11g 中的元组中的 null 进行比较
- java - 将哈希码转换为字符串后,我无法在 Java 中将其重新转换为 int
- excel - 计算多个范围的平均值
- flutter - 有没有办法让 Flutter macOS 应用的字符选择栏出现在正确的位置?
- vuejs2 - Vue.js (v.2) 仅在单击按钮后加载(获取)数据
- r - 条件均值和散点图
- r - 如何在预期时使用 testthat 将消息和警告静音
- java - Java Streams 验证是否只有一个结果
- php - 在 Woocommerce 结帐页面中显示自定义消息