如何使用 Jest 模拟 PerformanceObserver

问题描述 投票:0回答:1

我有一些代码,在构造函数中创建 PerformanceObserver,我试图找出如何模拟它,以便我可以断言

observe
正在观察者上被调用,并且还模拟正在生成的性能条目,这将导致回调被调用,并断言回调实际上被调用。构造函数中的代码如下所示:

    const { _tracking } = props;

    this.resourceObserver = new PerformanceObserver((list) => {
      const resourceEntries = list.getEntriesByType(
        'resource',
      ) as PerformanceResourceTiming[];

      for (const resource of resourceEntries) {
        resourceObserverCallback(_tracking, resource);
      }
      performance.clearResourceTimings();
    });
    this.resourceObserver.observe({ entryTypes: ['resource'] });

有人有什么建议吗?

编辑:

我可以断言,通过创建一个

observe
函数来在观察者上调用
createResourceObserver
,该函数返回传入的模拟或真实的
PerformanceObserver
,这解决了部分问题,但我仍然不确定如何在测试中锻炼观察者。

该函数如下所示:

createResourceObserver() {
    const { _mockObserver, _tracking } = this.props;

    if (_mockObserver) {
      return _mockObserver;
    }

    const observer = new PerformanceObserver((list) => {
      const resourceEntries = list.getEntriesByType(
        'resource',
      ) as PerformanceResourceTiming[];

      for (const resource of resourceEntries) {
        resourceObserverCallback(_tracking, resource);
      }
      performance.clearResourceTimings();
    });
    return observer;
  }
javascript mocking jestjs
1个回答
0
投票

这需要一个很长的答案。 工作游乐场示例链接位于最后。

要测试某些东西,您必须从外部隔离并注入所有外部依赖项。有不同的模式可以做到这一点。

您想要测试两件事 - 构造函数中发生的情况以及启动回调时发生的情况。我们将分别解决这些问题。 要测试您的

PerformanceObserverCallbackHandler
,您需要将其分离可调用的东西,并注入所有外部依赖项。

我们将隔离并隐藏 PerformanceObserver 在单例工厂后面,以便我们可以控制注入哪个实现以及何时注入。

这里是工厂的实现

class PerformanceObserverFactory {
  private instance: PerformanceObserver | undefined;
  create(callBack: PerformanceObserverCallback): PerformanceObserver {
    if (this.instance) {
      return this.instance;
    }
    return new PerformanceObserver(callBack);
  }

  setMockedIsntance(mock: PerformanceObserver) {
    if (this.instance !== undefined) {
      throw new Error(
        'Mocked instance is already set. Consider calling reset() in beforeEach()'
      );
    }
    this.instance = mock;
  }

  reset() {
    this.instance = undefined;
  }
}

const instance = new PerformanceObserverFactory();

export default instance;

现在,让我们拆开班级吧。一些外部依赖项没有被描述,所以我伪造了它们。请参阅

declare global
部分。这是消费者类的实现

import PerformanceObserverFactory from './PerformanceObserverFactory';

export type Handler = (
  tracking: string,
  resource: PerformanceResourceTiming
) => void;

declare global {
  var _tracking: string;
  var resourceObserverCallback: Handler;
}

export class ClassToBeTested {
  private resourceObserver: PerformanceObserver;

  constructor() {
    const callBack = this.getPerformanceObserverCallback(
      _tracking,
      resourceObserverCallback,
      performance
    );
    this.resourceObserver = PerformanceObserverFactory.create(callBack);

    this.resourceObserver.observe({ entryTypes: ['resource'] });
  }

  getPerformanceObserverCallback(
    tracking: string,
    handler: Handler,
    performance: Performance
  ): PerformanceObserverCallback {
    return (list) => {
      const resourceEntries = list.getEntriesByType(
        'resource'
      ) as PerformanceResourceTiming[];

      for (const resource of resourceEntries) {
        handler(tracking, resource);
      }
      performance.clearResourceTimings();
    };
  }
}

最后,我们有测试本身:

import { ClassToBeTested, Handler } from './ClassToBeTested';
import PerformanceObserverFactory from './PerformanceObserverFactory';
import { mock } from 'jest-mock-extended';

describe('ClassToBeTested.constructor', () => {
  beforeEach(() => {
    PerformanceObserverFactory.reset();
  });

  it('Should subscribe to PerformanceObserver with correct params', () => {
    const observeMock = jest.fn();
    PerformanceObserverFactory.setMockedIsntance({
      observe: observeMock,
    } as unknown as PerformanceObserver);

    const testSubject = new ClassToBeTested();

    expect(observeMock).toHaveBeenCalledWith({ entryTypes: ['resource'] });
  });
});

describe('ClassToBeTested.getPerformanceObserverCallback', () => {
  beforeEach(() => {
    PerformanceObserverFactory.reset();
  });

  it('Should call the resourceObserverCallback for eeach list entry and then call clearResourceTimings once', () => {
    const entryListResult: Array<PerformanceResourceTiming> = [
      { name: 'name 1' },
      { name: 'name 2' },
    ] as Array<PerformanceResourceTiming>;
    const resourceObserverCallbackMock = jest.fn();
    const _tracking = 'I do not know what this is';
    const performanceMock = mock<Performance>();
    const entryList = mock<PerformanceObserverEntryList>();
    entryList.getEntriesByType.mockReturnValue(entryListResult);

    PerformanceObserverFactory.setMockedIsntance({
      observe: jest.fn(),
    } as unknown as PerformanceObserver);

    const testSubject = new ClassToBeTested();

    const theCallback = testSubject.getPerformanceObserverCallback(
      _tracking,
      resourceObserverCallbackMock,
      performanceMock
    );

    //lets call it to test it
    theCallback(entryList, mock<PerformanceObserver>());

    expect(entryList.getEntriesByType).toHaveBeenCalledTimes(1);
    expect(resourceObserverCallbackMock).toHaveBeenCalledTimes(
      entryListResult.length
    );
    expect(performanceMock.clearResourceTimings).toHaveBeenCalled();

    // we check that each item in the result list is processed
    entryListResult.forEach((item, index) => {
      expect(resourceObserverCallbackMock).toHaveBeenNthCalledWith(
        index + 1,
        _tracking,
        item
      );
    });
  });
});

这是完整工作示例的链接。

工厂方法模式链接

单例模式链接

© www.soinside.com 2019 - 2024. All rights reserved.