如何使用 jasmine + karma 和 Angular 测试 HTMLImageElement.onload

Old*_*row 6 typescript karma-jasmine angular

我想为我正在从事的 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)
}
Run Code Online (Sandbox Code Playgroud)

现在,我只想编写一个测试来检查图像是否已加载以及数据 URL 是否已成功存储;就像是:

it('...', () => {
  expect(logoInstance.dataUrl).toBeTruthy()
}
Run Code Online (Sandbox Code Playgroud)

但是测试有时会失败(带有Expected '' to be truthy),有时会通过。我认为这是因为该onload函数是异步调用的,而当测试失败时,是因为在评估测试时图像尚未完成加载。但是,即使我asyncbeforeEach要测试的例程中使用的函数上,它仍然失败。

所以我的问题是,如何测试内部用于HTMLImageElement.onload异步加载图像的服务?有没有办法只 onload被调用评估测试?经过多次尝试,我在下面列出了我提出的完整代码:

客户信息.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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

客户信息.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()
    }
  )
})
Run Code Online (Sandbox Code Playgroud)

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


更新

在阅读了更多关于 jasmine 和 angular 的内容后,我现在明白我使用asyncwith 的尝试beforeEach是正确的,但是由于某种原因,我的测试行为没有改变 - 它有时仍然会失败并通过其他人。所以我尝试执行以下操作:

1.fakeAsync和tick

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

2. DoneFn 和间谍

我发现了beforeEachbeforeAllit以 的实例DoneFn作为参数,可用于指示评估已完成,是时候评估下一个测试了。所以我想出了一个想法,我可以使用间谍拦截调用Logo.setImageDoneFn在完成后调用。显然,做类似的事情是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()
  })
})
Run Code Online (Sandbox Code Playgroud)

但是,我还有另一个例外;这次声明Cannot set property '_dataUrl' of undefined,这是有道理的,因为setImageBackup没有定义任何东西,this因为它不是任何实例的方法,只是一个函数。尽管如此,我相信这是适用于我的案例的方法,也许如果我尝试监视HTMLImageElement.onload?然而,即使这种方法对我有用,我觉得我在这里做错了——我仍然错过了一些东西。我希望这些信息能让您更好地了解我的问题是什么以及我正在努力实现什么。