Я хочу написать тесты для проекта Angular, над которым я работаю, но я абсолютный новичок, когда речь идет о тестировании (теоретически), например, у меня есть сервис, который я искренне не знаю, как Я должен проверить. Эта услуга представляет собой просто контейнер, в котором хранится информация, относящаяся к клиенту, использующему программу (например, логотип компании, адрес и т. Д.) Для последующего поиска другими компонентами. Однако у него есть подпрограмма, которая загружает файл изображения логотипа с помощью HTMLImageElement
и преобразует его в URL-адрес данных с помощью HTMLCanvasElement
, например:
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
? Ниже приведен полный код того, что я придумал после нескольких попыток:
клиент-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)
}
}
}
клиент-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
была правильной, но по какой-то причине поведение моего теста не меняется - все равно не получается иногда и пропускает другие. Поэтому я попытался сделать следующее:
1. fakeAsync и отметьте
Я попытался использовать fakeAsync
вместо async
, что, как я понимаю, заставляет все асинхронные вызовы быть синхронными, но требует использования tick
, чтобы указать, что загрузка изображения завершена. Я провел рефакторинг своего теста, но получил исключение, которое читается как Error: Cannot make XHRs from within a fake async test
, что имеет смысл, поскольку HTMLImageElement.src
, вероятно, использует асинхронные вызовы XHR для загрузки файла изображения. Поэтому я не могу использовать этот метод с текущим состоянием CustomerInfoService
.
2. DoneFn и шпионы
Я обнаружил, что 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
? Тем не менее, даже если этот подход работает для меня, я чувствую, что я делаю что-то здесь не так, что я все еще что-то упускаю. Я надеюсь, что эта информация поможет вам лучше понять, в чем заключается моя проблема и чего я пытаюсь достичь.