我正在尝试模拟默认的DOM Image
对象,以对Angular服务进行单元测试。
该服务很简单,它检查“ webP”格式的支持:
import {Injectable} from '@angular/core';
import {Promise} from 'es6-promise';
import {environment} from '../../../../environments/environment';
@Injectable({
providedIn: 'root'
})
export class AppLoadService {
constructor() {
}
initializeApp(): Promise<any> {
return new Promise((resolve) => {
console.log(`initializeApp:: inside promise`);
if (typeof Image === 'undefined') {
console.log(`initializeApp:: Image undefined`);
resolve();
return;
}
const webP = new Image();
webP.onload = () => {
console.log(`initializeApp:: WebP support: true`);
environment.webP = true;
resolve();
};
webP.onerror = () => {
console.log(`initializeApp:: WebP support: false`);
resolve();
};
webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
});
}
}
我找到了一种检查webP支持的方法(运行Karma的铬是默认设置),以及一种检查Image undefined
上的后备情况的方法。
但是我找不到一种方法来检查onerror
后备...
这是我的规格文件:
import {TestBed} from '@angular/core/testing';
import {AppLoadService} from './app-load.service';
import {environment} from '../../../../environments/environment';
describe('AppLoadService', () => {
let service: AppLoadService;
const originalImage = Image;
beforeEach(() => TestBed.configureTestingModule({}));
beforeEach(() => {
service = TestBed.get(AppLoadService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
it('should resolve with webP', (done) => {
const test = () => {
Image = originalImage;
service.initializeApp().then(() => {
expect(environment.webP).toBe(true);
done();
});
};
test();
});
it('should resolve without webP (A)', (done) => {
const test = () => {
Image = undefined;
service.initializeApp().then(() => {
expect(environment.webP).toBe(false);
done();
});
};
test();
});
it('should resolve without webP (B)', (done) => {
// How to force Image to throw "onerror" ?
const test = () => {
Image = originalImage;
service.initializeApp().then(() => {
expect(environment.webP).toBe(false);
done();
});
};
test();
});
});
问题在文件末尾的should resolve without webP (B)
测试中。
此外,还有更好的方法来检查未定义的Image对象或onload
回调吗?
谢谢!
编辑
无法按原样工作,因此我更改了服务构造函数以提供“图像”依赖项。
constructor(@Inject('Image') image: typeof Image) {
this.image = image;
}
必须像这样加载模块:
providers: [
AppLoadService,
// [...]
{provide: 'Image', useValue: Image},
]
每个resolve()
现在都包含environment.webP
个结果。否则,个别测试会很痛苦,environment
在测试之前会被随机重写。
使用一个简单的模拟,它的工作原理如下:
import {TestBed} from '@angular/core/testing';
import {AppLoadService} from './app-load.service';
class MockImageFail {
public onerror: Function;
private _src: string;
set src(src) {
this._src = src;
if (this.onerror) {
this.onerror();
}
}
}
describe('AppLoadService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [{provide: 'Image', useValue: Image}]
});
});
it('should be created', () => {
const service = TestBed.get(AppLoadService);
expect(service).toBeTruthy();
});
it('should resolve with webP', (done) => {
const service = TestBed.get(AppLoadService);
service.initializeApp().then((supportWebP) => {
expect(supportWebP).toBe(true);
done();
});
});
it('should not resolve without webP (A)', (done) => {
TestBed.overrideProvider('Image', {useValue: undefined});
const service = TestBed.get(AppLoadService);
service.initializeApp().then((supportWebP) => {
expect(supportWebP).toBe(false);
done();
});
});
it('should not resolve without webP (B)', (done) => {
TestBed.overrideProvider('Image', {useValue: MockImageFail});
const service = TestBed.get(AppLoadService);
service.initializeApp().then((supportWebP) => {
expect(supportWebP).toBe(false);
done();
});
});
});
我对此并不满意,我敢肯定还有另一种更好的方法:/
答案 0 :(得分:0)
我有类似的需求,但我不想为 Image
创建一个提供程序,因为我只在一个地方需要它。但我仍然需要模拟 Image
才能编写我的规范。
我确实有 window
的现有提供程序,因此我决定改用它。我正在编写一个网络应用程序,所以我的“全局”实际上是 window
。这是根据 MDN:"In a web browser, any code which the script doesn't specifically start up as a background task has a Window as its global object. This is the vast majority of JavaScript code on the Web." 我将 new Image()
更改为 new this.window.Image()
并在我的构造函数中注入窗口,如下所示:
constructor(@Inject(WINDOW) private readonly window: any) {}
我的支票看起来像这样:
public canLoadImage(uri: string): Promise<boolean> {
const image = new this.window.Image();
this.promise = new Promise((resolve) => {
image.onload = () => resolve(true);
image.onerror = () => resolve(false);
});
image.src = `${uri}?${Math.random()}`;
return this.promise;
}
所以我能够通过像这样模拟和注入 window
来测试这个:
beforeEach(() => {
mockImage = {};
mockWindow = { Image: () => mockImage };
service = new MyService(mockWindow);
});
it('resolves false when youtube is not reachable', fakeAsync(() => {
const successSpy = jasmine.createSpyObject('success');
service.canLoadImage('https://example.com').then(successSpy);
mockImage.onerror();
flushMicrotasks();
expect(successSpy).toHaveBeenCalledWith(false);
}));
it('resolves true when youtube is reachable', fakeAsync(() => {
const successSpy = jasmine.createSpyObject('success');
service.canLoadImage('https://example.com').then(successSpy);
mockImage.onload();
flushMicrotasks();
expect(successSpy).toHaveBeenCalledWith(true);
}));
请注意,您可以轻松地将代码更改为在失败时拒绝,并通过直接调用 mockImage.onerror()
进行测试。
另请注意,您必须在调用 flushMicrotaskt
(或 tick
)的同一区域中创建承诺。这就是为什么我设置成功间谍,调用 canLoadImage
,并在同一个 fakeAsync
方法调用中调用模拟图像方法。
我这样做的一个原因是为了避免实际的外部调用来获取图像。因为我的 Image
实例只是一个可以累积效果的对象,所以我不必担心它实际加载图像。