如何使用茉莉花+业力和Angular测试HTMLImageElement.onload

时间:2018-11-05 23:26:27

标签: angular typescript karma-jasmine

我想为我正在研究的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()
    }
  )
})

非常感谢您的时间和帮助。


更新

在阅读了有关茉莉花和角质的更多信息之后,我现在知道我尝试将asyncbeforeEach结合使用是正确的,但是由于某些原因,测试的行为没有改变-有时仍然会失败,并通过其他人。因此,我尝试执行以下操作:

1。 fakeAsync和滴答声

我尝试使用fakeAsync代替async,据我所知,它强制所有异步调用都同步,但是需要使用tick来指示图像加载已完成。我重构了测试,但是遇到了一个读取Error: Cannot make XHRs from within a fake async test的异常,这很有意义,因为HTMLImageElement.src可能使用XHR异步调用来加载图像文件。因此,无法在当前状态CustomerInfoService中使用此方法。

2。 DoneFn和间谍

我发现beforeEachbeforeAllitDoneFn的一个实例作为参数,可以用来表示评估已经完成,是时候了评估下一个测试。因此,我想到了可以使用间谍来拦截对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呢?但是,即使这种方法对我有用,我仍然觉得我在这里做错了—我仍然缺少一些东西。希望这些信息能使您更好地了解我的问题是什么以及我要实现的目标。

0 个答案:

没有答案