Как протестировать HTMLImageElement.onload с помощью jasmine + karma и Angular - PullRequest
0 голосов
/ 06 ноября 2018

Я хочу написать тесты для проекта 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? Тем не менее, даже если этот подход работает для меня, я чувствую, что я делаю что-то здесь не так, что я все еще что-то упускаю. Я надеюсь, что эта информация поможет вам лучше понять, в чем заключается моя проблема и чего я пытаюсь достичь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...