我想为我正在研究的Angular项目编写测试,但是在测试方面(从理论上来说)我是一个绝对的初学者,例如,我有一个服务,我真诚地不知道如何我应该测试。该服务只是一个容器,其中存储了使用该程序针对客户的特定信息(例如公司徽标,地址等),以供以后其他组件检索。但是,它有一个例程,可以使用HTMLImageElement
加载徽标图像文件,并使用HTMLCanvasElement
将其转换为数据URL,如下所示:
const image = new Image
image.onload = function(event) {
const self = <HTMLImageElement> this
const canvas = document.createElement('canvas')
canvas.width = self.naturalWidth
canvas.height = self.naturalHeight
canvas.getContext('2d').drawImage(self, 0, 0)
logoInstance._dataUrl = canvas.toDataURL('image/jpeg', 0.9)
}
现在,我只想编写一个测试来检查图像是否已加载以及数据URL是否已成功存储;像这样:
it('...', () => {
expect(logoInstance.dataUrl).toBeTruthy()
}
但是测试有时会失败(使用Expected '' to be truthy
)并且有时会通过。我认为这是因为onload
函数是异步调用的,并且当测试失败时,这是因为在评估测试时图像尚未完成加载。但是,即使我在要测试的例程中启动的async
函数上使用beforeEach
,它仍然会失败。
所以我的问题是,如何测试内部使用HTMLImageElement.onload
异步加载图像的服务?有没有一种方法只能在调用 onload
之后评估测试?经过几次尝试,我将在下面提供完整的代码:
customer-info.ts
import { Injectable } from '@angular/core'
class Logo {
private _dataUrl = ''
get dataUrl(): string {
return this._dataUrl
}
setImage(image: HTMLImageElement): void {
const canvas = document.createElement('canvas')
canvas.width = image.naturalWidth
canvas.height = image.naturalHeight
canvas.getContext('2d').drawImage(image, 0, 0)
this._dataUrl = canvas.toDataURL('image/jpeg', 0.9)
}
}
@Injectable()
export class CustomerInfoService {
readonly logo = new Logo
load(): void {
const logoImage = new Image
logoImage.onload = this.getOnImageLoadedCallback(this.logo)
logoImage.src = './assets/images/customer-logo-lq.jpg'
}
private getOnImageLoadedCallback(logo: Logo): (event: Event) => void {
return function(event) {
const self = <HTMLImageElement> this
logo.setImage(self)
}
}
}
customer-info.spec.ts
import { CustomerInfoService } from './customer-info'
import { async } from '@angular/core/testing'
describe('CoreModule.CustomerInfoService', () => {
let service: CustomerInfoService = null
beforeEach(async(() => {
service = new CustomerInfoService
service.load()
}))
it(
'Should load the logo image and compute its data URL',
() => {
expect(service.logo.dataUrl).toBeTruthy()
}
)
})
非常感谢您的时间和帮助。
在阅读了有关茉莉花和角质的更多信息之后,我现在知道我尝试将async
与beforeEach
结合使用是正确的,但是由于某些原因,测试的行为没有改变-有时仍然会失败,并通过其他人。因此,我尝试执行以下操作:
我尝试使用fakeAsync
代替async
,据我所知,它强制所有异步调用都同步,但是需要使用tick
来指示图像加载已完成。我重构了测试,但是遇到了一个读取Error: Cannot make XHRs from within a fake async test
的异常,这很有意义,因为HTMLImageElement.src
可能使用XHR异步调用来加载图像文件。因此,无法在当前状态CustomerInfoService
中使用此方法。
我发现beforeEach
,beforeAll
和it
将DoneFn
的一个实例作为参数,可以用来表示评估已经完成,是时候了评估下一个测试。因此,我想到了可以使用间谍来拦截对Logo.setImage
的调用并在完成后调用DoneFn
的想法。显然,执行spyOn(...).and.callThrough().and.callFake()
之类的操作不起作用。所以我尝试这样做:
describe('CoreModule.CustomerInfoService', () => {
beforeEach((done: DoneFn) => {
TestBed.configureTestingModule({
imports: [
HttpClientModule
],
providers: [
CustomerInfoService
]
})
service = TestBed.get(CustomerInfoService)
const setImageBackup = service.logo.setImage
spyOn(service.logo, 'setImage').and.callFake((image) => {
setImageBackup(image)
done()
})
service.load()
})
it('...', () => {
expect(service.logo.setImage).toHaveBeenCalled()
expect(service.logo.dataUrl).not.toBeNull()
})
})
但是,我还有另一个例外。这次说明Cannot set property '_dataUrl' of undefined
是有道理的,因为setImageBackup
没有为this
定义任何内容,因为它不是任何实例的方法,而只是一个函数。尽管如此,我仍然认为这可能适用于我的情况,也许如果我尝试监视HTMLImageElement.onload
呢?但是,即使这种方法对我有用,我仍然觉得我在这里做错了—我仍然缺少一些东西。希望这些信息能使您更好地了解我的问题是什么以及我要实现的目标。