玩笑和打字稿中交叉观察者的模拟

Asi*_*K T 6 mocking typescript jestjs intersection-observer react-testing-library

如果我们将 jest 与 typescript 一起使用,其中使用了相交观察器,则相交观察器的模拟将变得困难。到目前为止我处于:


beforeEach(() => {
  // IntersectionObserver isn't available in test environment
  const mockIntersectionObserver = class {
    observe() {
      console.log(this);
    }

    unobserve() {
      console.log(this);
    }

    disconnect() {
      console.log(this);
    }

    root = null

    rootMargin = '0'

    thresholds=[1]

    takeRecords=() => ([{
      isIntersecting: true,
      boundingClientRect: true,
      intersectionRatio: true,
      intersectionRect: true,
      rootBounds: true,
      target: true,
       time: true,
    }])
  };
  window.IntersectionObserver = mockIntersectionObserver;
});
Run Code Online (Sandbox Code Playgroud)

但这仍然会引发错误,例如:

Type 'typeof mockIntersectionObserver' is not assignable to type '{ new (callback: IntersectionObserverCallback, options?: IntersectionObserverInit | undefined): IntersectionObserver; prototype: IntersectionObserver; }'.
  The types returned by 'prototype.takeRecords()' are incompatible between these types.
    Type '{ isIntersecting: boolean; boundingClientRect: boolean; intersectionRatio: boolean; intersectionRect: boolean; rootBounds: boolean; target: boolean; time: boolean; }[]' is not assignable to type 'IntersectionObserverEntry[]'.
      Type '{ isIntersecting: boolean; boundingClientRect: boolean; intersectionRatio: boolean; intersectionRect: boolean; rootBounds: boolean; target: boolean; time: boolean; }' is not assignable to type 'IntersectionObserverEntry'.
        Types of property 'boundingClientRect' are incompatible.
          Type 'boolean' is not assignable to type 'DOMRectReadOnly'.ts(2322
Run Code Online (Sandbox Code Playgroud)

我可以继续向每个元素添加正确的类型,但是有更好的方法吗?

如何将交叉口观察者添加到 jest 环境中?我想,这比这样嘲笑要好得多。

cbu*_*mer 7

这对我们有用

/* eslint-disable class-methods-use-this */

export default class {
  readonly root: Element | null;

  readonly rootMargin: string;

  readonly thresholds: ReadonlyArray<number>;

  constructor() {
    this.root = null;
    this.rootMargin = '';
    this.thresholds = [];
  }

  disconnect() {}

  observe() {}

  takeRecords(): IntersectionObserverEntry[] {
    return [];
  }

  unobserve() {}
}
Run Code Online (Sandbox Code Playgroud)

然后安装然后做

import MockIntersectionObserver from './MockIntersectionObserver';
window.IntersectionObserver = MockIntersectionObserver;
Run Code Online (Sandbox Code Playgroud)


Est*_*ask 4

一般来说,模拟应该严格遵循IntersectionObserver规范IntersectionObserverEntry,但模拟可以根据用途进行精简。

如果正确的类型使事情变得更加复杂,则可能没有必要在模拟中保留类型安全。在这种情况下,类型错误表明模拟中存在错误。从参考文献中可以看出IntersectionObserverEntry,只有isIntersecting属性应该是布尔值,而boundingClientRect应该是对象,因此为其余部分提供布尔值并忽略类型问题可能会导致模拟实现无意中不起作用。

使用常规类进行模拟是不切实际的,因为它缺乏 Jest 间谍提供的功能,例如由框架控制的调用断言和模拟实现。

一个简单的实现是:

window.IntersectionObserver = jest.fn(() => ({
  takeRecords: jest.fn(),
  ...
}));
Run Code Online (Sandbox Code Playgroud)

缺点是,当无法直接访问实例或者需要在实例化后立即完成此操作时,无法更改模拟类成员的实现。这需要在需要时替换整个类的实现。

因此,让 Jest 监视一个具有可以在实例化之前访问的原型链的类是有益的。可以利用 Jest 自动模拟来实现此目的,这允许定义get只读属性的访问器,这些属性可以像任何其他 Jest 间谍一样更改实现:

class IntersectionObserverStub {
  get root() {} // read-only property 
  takeRecords() { /* implementation is ignored */ } // stub method
  observe() {}
  ...
}

jest.doMock('intersection-observer-mock', () => IntersectionObserverStub, { virtual: true });

window.IntersectionObserver = jest.requireMock('intersection-observer-mock');

jest.spyOn(IntersectionObserver.prototype, 'root', 'get').mockReturnValue(null);
// jest.spyOn(IntersectionObserver.prototype, 'takeRecords').mockImplementation(() => ({...}));
Run Code Online (Sandbox Code Playgroud)

这会导致生成模拟类实现,该类实现是 Jest 间谍原型,其方法是无操作间谍。get访问器保持原样,但由于它们存在,以后可以用 来模拟它们spyOn(..., 'get')。像这样的方法takeRecords可能特定于实例,可以在没有默认实现的情况下保留并就地进行模拟,undefined当意外调用它时,它返回的结果可能会比随机预定义值更干净的错误输出。

jest.spyOn(IntersectionObserver.prototype, 'root', 'get').mockReturnValueOnce(someDocument);
const mockedEntries = [{
  isIntersecting: true,
  boundingClientRect: { x: 10, y: 20, width: 30, height: 40, ... },
  ...
}];
IntersectionObserver.prototype.takeRecords.mockReturnValueOnce(mockedEntries );

// code that instantiates IntersectionObserver and possibly uses mocked values immediately

expect(IntersectionObserver.prototype.observe).toBeCalledWith(...);
expect(IntersectionObserver).toBeCalledWith(expect.any(Function)); // callback arg
let [callback] = IntersectionObserver.mock.calls[0]
callback(mockedEntries); // test a callback 
Run Code Online (Sandbox Code Playgroud)