【问题标题】:How to mock Image object with Karma (Angular 7)?如何使用 Karma(Angular 7)模拟 Image 对象?
【发布时间】:2018-11-13 09:07:14
【问题描述】:

我正在尝试模拟默认 DOM Image 对象以对 Angular 服务进行单元测试。

服务很简单,它检查“webP”格式支持:

import {Injectable} from '@angular/core';
import {Promise} from 'es6-promise';
import {environment} from '../../../../environments/environment';

@Injectable({
  providedIn: 'root'
})
export class AppLoadService {

  constructor() {
  }

  initializeApp(): Promise<any> {
    return new Promise((resolve) => {
      console.log(`initializeApp:: inside promise`);
      if (typeof Image === 'undefined') {
        console.log(`initializeApp:: Image undefined`);
        resolve();
        return;
      }
      const webP = new Image();
      webP.onload = () => {
        console.log(`initializeApp:: WebP support: true`);
        environment.webP = true;
        resolve();
      };
      webP.onerror = () => {
        console.log(`initializeApp:: WebP support: false`);
        resolve();
      };
      webP.src = 'data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP8bA//LwYAAA';
    });
  }
}

我找到了一种检查 webP 支持的方法(默认在运行 Karma 的 chromium 中),以及一种检查 Image undefined 的回退方法。

但我找不到检查onerror 后备的方法...

这是我的规范文件:

import {TestBed} from '@angular/core/testing';
import {AppLoadService} from './app-load.service';
import {environment} from '../../../../environments/environment';

describe('AppLoadService', () => {
  let service: AppLoadService;
  const originalImage = Image;

  beforeEach(() => TestBed.configureTestingModule({}));

  beforeEach(() => {
    service = TestBed.get(AppLoadService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should resolve with webP', (done) => {
    const test = () => {
      Image = originalImage;
      service.initializeApp().then(() => {
        expect(environment.webP).toBe(true);
        done();
      });
    };
    test();
  });

  it('should resolve without webP (A)', (done) => {
    const test = () => {
      Image = undefined;
      service.initializeApp().then(() => {
        expect(environment.webP).toBe(false);
        done();
      });
    };
    test();
  });

  it('should resolve without webP (B)', (done) => {
    // How to force Image to throw "onerror" ?
    const test = () => {
      Image = originalImage;
      service.initializeApp().then(() => {
        expect(environment.webP).toBe(false);
        done();
      });
    };
    test();
  });
});

问题在should resolve without webP (B)测试,在文件末尾..

此外,有没有更好的方法来检查未定义的 Image 对象或onload 回调?

谢谢!


编辑

无法让它按原样工作,所以我更改服务构造函数以提供“图像”依赖项。

constructor(@Inject('Image') image: typeof Image) {
  this.image = image;
}

必须像这样加载模块:

providers: [
  AppLoadService,
  // [...]
  {provide: 'Image', useValue: Image},
]

每个resolve() 现在都包含environment.webP 结果。否则,单独测试真的很痛苦,environment 在测试前被随机重写。

使用一个简单的 Mock,它的工作原理如下:

import {TestBed} from '@angular/core/testing';
import {AppLoadService} from './app-load.service';

class MockImageFail {
  public onerror: Function;
  private _src: string;

  set src(src) {
    this._src = src;
    if (this.onerror) {
      this.onerror();
    }
  }
}

describe('AppLoadService', () => {

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [{provide: 'Image', useValue: Image}]
    });
  });

  it('should be created', () => {
    const service = TestBed.get(AppLoadService);
    expect(service).toBeTruthy();
  });

  it('should resolve with webP', (done) => {
    const service = TestBed.get(AppLoadService);
    service.initializeApp().then((supportWebP) => {
      expect(supportWebP).toBe(true);
      done();
    });
  });

  it('should not resolve without webP (A)', (done) => {
    TestBed.overrideProvider('Image', {useValue: undefined});
    const service = TestBed.get(AppLoadService);
    service.initializeApp().then((supportWebP) => {
      expect(supportWebP).toBe(false);
      done();
    });
  });

  it('should not resolve without webP (B)', (done) => {
    TestBed.overrideProvider('Image', {useValue: MockImageFail});
    const service = TestBed.get(AppLoadService);
    service.initializeApp().then((supportWebP) => {
      expect(supportWebP).toBe(false);
      done();
    });
  });
});

我对此并不满意,我相信还有另一种更好的方法:/

【问题讨论】:

    标签: angular image dom karma-jasmine angular7


    【解决方案1】:

    我也有类似的需求,但我不想只为Image 创建一个提供程序,因为我只在一个地方需要它。但我仍然需要模拟 Image 才能编写我的规范。

    我确实有一个现有的window 提供程序,所以我决定改用它。我正在编写一个网络应用程序,所以我的“全球”实际上是window。这是根据 MDN:"In a web browser, any code which the script doesn't specifically start up as a background task has a Window as its global object. This is the vast majority of JavaScript code on the Web." 我将 new Image() 更改为 new this.window.Image() 并像这样在构造函数中注入窗口:

    constructor(@Inject(WINDOW) private readonly window: any) {}
    

    然后我的支票是这样的:

    public canLoadImage(uri: string): Promise<boolean> {
      const image = new this.window.Image();
      this.promise = new Promise((resolve) => {
        image.onload = () => resolve(true);
        image.onerror = () => resolve(false);
      });
      image.src = `${uri}?${Math.random()}`;
      return this.promise;
    }
    

    所以我可以像这样模拟和注入window 来测试这个:

    beforeEach(() => {
      mockImage = {};
      mockWindow = { Image: () => mockImage };
      service = new MyService(mockWindow);
    });
     
    it('resolves false when youtube is not reachable', fakeAsync(() => {
      const successSpy = jasmine.createSpyObject('success');
      service.canLoadImage('https://example.com').then(successSpy);
      mockImage.onerror();
      flushMicrotasks();
    
      expect(successSpy).toHaveBeenCalledWith(false);
    }));
    
    it('resolves true when youtube is reachable', fakeAsync(() => {
      const successSpy = jasmine.createSpyObject('success');
      service.canLoadImage('https://example.com').then(successSpy);
      mockImage.onload();
      flushMicrotasks();
    
      expect(successSpy).toHaveBeenCalledWith(true);
    }));
    

    请注意,您可以轻松地将代码更改为在失败时拒绝,并通过直接调用 mockImage.onerror() 进行测试。

    还要注意,您必须在调用flushMicrotaskt(或tick)的同一区域中创建承诺。这就是为什么我设置成功间谍,调用canLoadImage,并在同一个fakeAsync 方法调用中调用模拟图像方法。

    我这样做的一个原因是为了避免实际的外部调用来获取图像。因为我的Image 实例只是一个用于累积效果的对象,所以我不必担心它实际上会加载图像。

    【讨论】:

      猜你喜欢
      • 2023-04-07
      • 1970-01-01
      • 2020-07-04
      • 1970-01-01
      • 1970-01-01
      • 2017-09-04
      • 2016-01-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多