繁体   English   中英

如何使用 Jest 测试 img.onload?

[英]How to test img.onload using Jest?

我被img.onload事件测试困住了。 我知道这是一个异步操作,它应该被嘲笑,但我仍然无法弄清楚如何解决这个问题。 我也见过几个类似的案例,但它们与这个不同。

以前访问过:

测试代码:

  function funcToTest(img, callback) {
    const img = new Image()
    img.src = img

    img.onload = () => {
      callback(true) // should return callback with true on finish
    }

    img.onerror = (e) => {
      callback(false) // should return callback with false on error
      console.log(e) 
    }
  }

  funcToTest()

测试环境:

describe('tet it', () => {
  it('test', done => {
    const callback = status => {
      expect(status).toEqual(true) // but nothing happen
      done()
    }

    funcToTest('some_image', callback)
  })
})

另外我在完成时遇到错误:

    Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.
        > 2 |   it('test', done => {...

提前致谢!

虽然回答了这个问题,但我不同意解决方案的目的。

我们不应该编写测试来通过覆盖率,我们应该编写测试来证明一个观点,并确保代码片段按预期运行。

测试它的方法是模拟 Image 构造函数并将其替换为将调用onload函数的内容。

describe('tet it', () => {

  it('test', done => {
    global.Image = class {
      constructor() {
        setTimeout(() => {
          this.onload(); // simulate success
        }, 100);
      }
    }

    const callback = status => {
      done()
    }

    funcToTest('some_image', callback)
  })
})

该方法只是假设“浏览器”将在 100 毫秒内下载图像,如果您需要在测试之间共享此行为,您可以调整失败的代码,或者将部分代码移动到beforeEach

如果您在Imageonload事件处理程序中有一些逻辑并想测试它是否被正确应用,您实际上可以使用Canvas 以编程方式创建一个图像blob

考虑这个函数:

const imageDimensions = (
  file: File
): Promise<{ width: number; height: number }> =>
  new Promise((resolve, reject) => {
    const img = new Image()

    img.onload = () => {
      const { naturalWidth: width, naturalHeight: height } = img
      resolve({ width, height })
    }

    img.onerror = () => {
      reject('There is something wrong with this image')
    }

    img.src = URL.createObjectURL(file)
  })`

因此,给定一个文件(blob),它会返回一个解析为具有维度的对象的Promise 或因错误而拒绝。

我们如何测试onload部分? 您可以在测试中以编程方式创建 blob,但new Blob(['a'.repeat(10)], { type: 'image/jpeg' })不会触发onload ,因为这不是真正的图像。

不需要模拟任何东西,使用 JSDOM 或类似的东西。 帆布来拯救! 这可以像这样简单地测试:

describe('imageDimensions', () => {
  it('should resolve with correct dimensions given an IMAGE blob', done => {
    // although this canvas has no actual graphics, it is still an image and it contains image metadata, thus onload() will be fired
    const canvas = document.createElement('canvas')
    canvas.width = 10
    canvas.height = 10

    canvas.toBlob(
      async blob => {
        const { width, height } = await component.imageDimensions(blob)
        expect(width).toBe(10)
        expect(height).toBe(10)
        done()
      },
      'image/jpeg',
      0.1
    )
  })

  it('should reject with an error if the file provided does NOT seem to be an image', async () => {
    const file = new Blob(['a'.repeat(10)], { type: 'application/pdf' })

    try {
      await component.imageDimensions(file)
    } catch (error) {
      expect(error).toBe('There is something wrong with this image')
    }
  })
})

在这个例子中,Canvas 正在被创建,然后被转换为一个 blob,这类似于用户选择某个图像文件时由浏览器生成的 blob。

PS 这是 Jest 和 Jasmine 兼容的。

要测试的功能:

index.ts

function funcToTest(imgUrl: string, callback: Function) {
  const img = new Image();
  img.src = imgUrl;

  img.onload = () => {
    callback(true);
  };

  img.onerror = e => {
    callback(false);
    console.log(e);
  };

  return img;
}

export { funcToTest };

  • 单元测试解决方案1:
/**
 * @jest-environment jsdom
 */

import { funcToTest } from './';

describe('test suites', () => {
  it('onload', done => {
    const callback = jest.fn(status => {
      expect(status).toBe(true);
      done();
    });

    const imageUrl = 'https://github.com/mrdulin';
    const img = funcToTest(imageUrl, callback);
    if (img.onload) {
      const event: any = {};
      img.onload(event);
    }
  });

  it('onerror', done => {
    const consoleLogSpyOn = jest.spyOn(console, 'log');
    const callback = jest.fn(status => {
      expect(status).toBe(false);
      done();
    });

    const imageUrl = 'https://github.com/mrdulin';
    const img = funcToTest(imageUrl, callback);
    if (img.onerror) {
      const event: any = { message: 'some error' };
      img.onerror(event);
      expect(consoleLogSpyOn).toBeCalledWith(event);
    }
    consoleLogSpyOn.mockRestore();
  });
});

单元测试结果和覆盖率:

 PASS  src/stackoverflow/57092154/index.spec.ts
  test suites
    ✓ onload (8ms)
    ✓ onerror (8ms)

  console.log node_modules/jest-mock/build/index.js:860
    { message: 'some error' }

----------|----------|----------|----------|----------|-------------------|
File      |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files |      100 |      100 |      100 |      100 |                   |
 index.ts |      100 |      100 |      100 |      100 |                   |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.821s

这是完整的演示: https : //github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57092154

更新:未删除的答案,单元测试解决方案 2

您可以使用Object.defineProperty()方法为Image.prototype.onload方法创建 getter 和 setter。 然后,您可以在测试用例中获取onload函数并手动执行它。

index.test.ts

import { funcToTest } from './';

describe('57092154', () => {
  let onloadRef: Function | undefined;
  let onerrorRef: Function | undefined;
  beforeAll(() => {
    Object.defineProperty(Image.prototype, 'onload', {
      get() {
        return this._onload;
      },
      set(onload: Function) {
        onloadRef = onload;
        this._onload = onload;
      },
    });
    Object.defineProperty(Image.prototype, 'onerror', {
      get() {
        return this._onerror;
      },
      set(onerror: Function) {
        onerrorRef = onerror;
        this._onerror = onerror;
      },
    });
  });
  it('should handle onload event', () => {
    const callback = jest.fn();
    funcToTest('./test.png', callback);
    onloadRef!();
    expect(callback).toBeCalledWith(true);
  });

  it('should handle onerror event', () => {
    const callback = jest.fn();
    const logSpy = jest.spyOn(console, 'log').mockImplementation(() => 'suppress error');
    funcToTest('./test.png', callback);
    const mErr = new Error('network');
    onerrorRef!(mErr);
    expect(callback).toBeCalledWith(false);
    expect(logSpy).toBeCalledWith(mErr);
  });
});

单元测试结果:

 PASS  examples/57092154/index.test.ts
  57092154
    ✓ should handle onload event (4 ms)
    ✓ should handle onerror event

----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.741 s

@Patrick 答案的打字稿版本:

type ImageConstructor = new (
  width?: number | undefined,
  height?: number | undefined
) => HTMLImageElement;

describe('1234', () => {
  global.Image = class {
    onload: () => void;

    constructor() {
      this.onload = jest.fn();
      setTimeout(() => {
        this.onload();
      }, 50);
    }
  } as unknown as ImageConstructor;

 it("should call onload", () => {})

)};

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM