JS - 测试使用IntersectionObserver的代码

Rai*_*baz 12 javascript unit-testing qunit intersection-observer

我已(书面相当差)一个JavaScript组件在我的应用程序来处理无限滚动分页,我试图重写它使用的IntersectionObserver,如所描述这里,但是我遇到在测试它的问题.

有没有办法在QUnit测试中驱动观察者的行为,即用我的测试中描述的一些条目触发观察者回调?

我提出的一个可能的解决方案是在组件的原型中公开回调函数,并在我的测试中直接调用它,如下所示:

InfiniteScroll.prototype.observerCallback = function(entries) {
    //handle the infinite scroll
}

InfiniteScroll.prototype.initObserver = function() {
    var io = new IntersectionObserver(this.observerCallback);
    io.observe(someElements);
}

//In my test
var component = new InfiniteScroll();
component.observerCallback(someEntries);
//Do some assertions about the state after the callback has been executed
Run Code Online (Sandbox Code Playgroud)

我真的不喜欢这种方法,因为它暴露了组件IntersectionObserver内部使用的事实,这是我认为客户端代码不应该看到的实现细节,所以有没有更好的方法来测试它?

奖金喜欢不使用jQuery的解决方案:)

Rob*_*uch 17

jest.setup.js文件中,使用以下实现模拟 IntersectionObserver:

global.IntersectionObserver = class IntersectionObserver {
  constructor() {}

  disconnect() {
    return null;
  }

  observe() {
    return null;
  }

  takeRecords() {
    return null;
  }

  unobserve() {
    return null;
  }
};
Run Code Online (Sandbox Code Playgroud)

除了使用Jest Setup File 之外,您还可以直接在测试中或在 beforeAll,beforeEach 块中进行此模拟。

  • 有 TypeScript 友好的版本吗?它有效,但我收到警告...即在 `global.IntersectionObserver` 上:`类型 'typeof IntersectionObserver' 不可分配给类型 '{ new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit | undefined): IntersectionObserver; 原型:IntersectionObserver;}'.` (2认同)

Rob*_*ina 16

这是基于先前答案的另一种选择,您可以在beforeEach方法内部或.test.js文件开头运行它。

您还可以将参数传递给setupIntersectionObserverMock以模拟observe和/或unobserve使用jest.fn()模拟函数监视它们的方法。

/**
 * Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely
 * on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`.
 * @param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty`
 * overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only
 * mock the intersection observer, but its methods.
 */
export function setupIntersectionObserverMock({
  root = null,
  rootMargin = '',
  thresholds = [],
  disconnect = () => null,
  observe = () => null,
  takeRecords = () => [],
  unobserve = () => null,
} = {}) {
  class MockIntersectionObserver {
    constructor() {
      this.root = root;
      this.rootMargin = rootMargin;
      this.thresholds = thresholds;
      this.disconnect = disconnect;
      this.observe = observe;
      this.takeRecords = takeRecords;
      this.unobserve = unobserve;
    }
  }

  Object.defineProperty(window, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver
  });

  Object.defineProperty(global, 'IntersectionObserver', {
    writable: true,
    configurable: true,
    value: MockIntersectionObserver
  });
}
Run Code Online (Sandbox Code Playgroud)

对于打字稿:

/**
 * Utility function that mocks the `IntersectionObserver` API. Necessary for components that rely
 * on it, otherwise the tests will crash. Recommended to execute inside `beforeEach`.
 * @param intersectionObserverMock - Parameter that is sent to the `Object.defineProperty`
 * overwrite method. `jest.fn()` mock functions can be passed here if the goal is to not only
 * mock the intersection observer, but its methods.
 */
export function setupIntersectionObserverMock({
  root = null,
  rootMargin = '',
  thresholds = [],
  disconnect = () => null,
  observe = () => null,
  takeRecords = () => [],
  unobserve = () => null,
} = {}): void {
  class MockIntersectionObserver implements IntersectionObserver {
    readonly root: Element | null = root;
    readonly rootMargin: string = rootMargin;
    readonly thresholds: ReadonlyArray < number > = thresholds;
    disconnect: () => void = disconnect;
    observe: (target: Element) => void = observe;
    takeRecords: () => IntersectionObserverEntry[] = takeRecords;
    unobserve: (target: Element) => void = unobserve;
  }

  Object.defineProperty(
    window,
    'IntersectionObserver', {
      writable: true,
      configurable: true,
      value: MockIntersectionObserver
    }
  );

  Object.defineProperty(
    global,
    'IntersectionObserver', {
      writable: true,
      configurable: true,
      value: MockIntersectionObserver
    }
  );
}
Run Code Online (Sandbox Code Playgroud)


Kev*_*cke 10

由于我们正在使用 TypeScript 和 React (tsx) 的配置,因此没有任何已发布的回答对我有用。这是最终有效的方法:

beforeEach(() => {
  // IntersectionObserver isn't available in test environment
  const mockIntersectionObserver = jest.fn();
  mockIntersectionObserver.mockReturnValue({
    observe: () => null,
    unobserve: () => null,
    disconnect: () => null
  });
  window.IntersectionObserver = mockIntersectionObserver;
});
Run Code Online (Sandbox Code Playgroud)

  • 在 React Typescript 项目中也为我工作过。太感谢了! (3认同)

Ste*_*fan 7

2019年的同样问题是我如何解决的:

import ....

describe('IntersectionObserverMokTest', () => {
  ...
  const observeMock = {
    observe: () => null,
    disconnect: () => null // maybe not needed
  };

  beforeEach(async(() => {
    (<any> window).IntersectionObserver = () => observeMock;

    ....
  }));


  if(' should run the Test without throwing an error for the IntersectionObserver', () => {
    ...
  })
});
Run Code Online (Sandbox Code Playgroud)

因此,我使用observe(和disconnect)方法创建了一个模拟对象,并覆盖IntersectionObserver了窗口对象。根据您的用法,您可能必须覆盖其他功能(请参阅:https : //developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Browser_compatibility

该代码受https://gist.github.com/ianmcnally/4b68c56900a20840b6ca840e2403771c启发,但未使用jest