在 React 函数组件中模拟 refs

IT技术 reactjs jestjs enzyme react-hooks
2021-03-28 06:21:46

我有一个 React 函数组件,它在它的一个子组件上有一个引用。ref 是通过useRef.

我想用浅渲染器测试组件。我必须以某种方式模拟 ref 来测试其余的功能。

我似乎找不到任何方法来获得这个 ref 并嘲笑它。我尝试过的事情

  • 通过 childs 属性访问它。React 不喜欢那样,因为 ref 并不是真正的props

  • 模拟 useRef. 我尝试了多种方法,但只有在我的实现使用时才能让它与间谍一起工作React.useRef

我看不到任何其他方式可以让裁判嘲笑它。在这种情况下我必须使用 mount 吗?

我无法发布真实场景,但我构建了一个小示例

it('should test', () => {
    const mock = jest.fn();
    const component = shallow(<Comp onHandle={mock}/>);


    // @ts-ignore
    component.find('button').invoke('onClick')();

    expect(mock).toHaveBeenCalled();
});

const Comp = ({onHandle}: any) => {
    const ref = useRef(null);

    const handleClick = () => {
        if (!ref.current) return;

        onHandle();
    };

    return (<button ref={ref} onClick={handleClick}>test</button>);
};
4个回答

这是我的单元测试策略,jest.spyOnuseRef钩子使用方法间谍

index.tsx

import React from 'react';

export const Comp = ({ onHandle }: any) => {
  const ref = React.useRef(null);

  const handleClick = () => {
    if (!ref.current) return;

    onHandle();
  };

  return (
    <button ref={ref} onClick={handleClick}>
      test
    </button>
  );
};

index.spec.tsx

import React from 'react';
import { shallow } from 'enzyme';
import { Comp } from './';

describe('Comp', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });
  it('should do nothing if ref does not exist', () => {
    const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: null });
    const component = shallow(<Comp></Comp>);
    component.find('button').simulate('click');
    expect(useRefSpy).toBeCalledWith(null);
  });

  it('should handle click', () => {
    const useRefSpy = jest.spyOn(React, 'useRef').mockReturnValueOnce({ current: document.createElement('button') });
    const mock = jest.fn();
    const component = shallow(<Comp onHandle={mock}></Comp>);
    component.find('button').simulate('click');
    expect(useRefSpy).toBeCalledWith(null);
    expect(mock).toBeCalledTimes(1);
  });
});

100% 覆盖率的单元测试结果:

 PASS  src/stackoverflow/57805917/index.spec.tsx
  Comp
    ✓ should do nothing if ref does not exist (16ms)
    ✓ should handle click (3ms)

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

源代码:https : //github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/57805917

如果组件有多个useRefs 怎么办?
2021-06-02 06:21:46
@Shadowlauch 您的评论是关键
2021-06-09 06:21:46
它似乎并没有真正模拟 ref 返回一个{current: null}(至少在使用 mount 时不是)。
2021-06-09 06:21:46
我在这个解决方案中遇到的问题,它迫使我使用React.useRef. 但我猜这是浅浅的唯一方法。
2021-06-18 06:21:46
@Shadowlauch 检查这个答案:stackoverflow.com/a/61789113/6463558
2021-06-22 06:21:46

slideshowp2 的解决方案对我不起作用,所以最终使用了不同的方法:

解决了它

  1. 引入 useRef 可选props,默认使用 react 的props
import React, { useRef as defaultUseRef } from 'react'
const component = ({ useRef = defaultUseRef }) => {
  const ref = useRef(null)
  return <RefComponent ref={ref} />
}
  1. 在测试模拟 useRef
const mockUseRef = (obj: any) => () => Object.defineProperty({}, 'current', {
  get: () => obj,
  set: () => {}
})

// in your test
...
    const useRef = mockUseRef({ refFunction: jest.fn() })
    render(
      <ScanBarcodeView onScan={handleScan} useRef={useRef} />,
    )
...
2021-06-01 06:21:46

我无法得到一些答案,所以我最终将我的 useRef 移动到它自己的函数中,然后模拟该函数:

// imports the refCaller from this file which then be more easily mocked
import { refCaller as importedRefCaller } from "./current-file";

// Is exported so it can then be imported within the same file
/**
* Used to more easily mock ref
* @returns ref current
*/
export const refCaller = (ref) => {
    return ref.current;
};

const Comp = () => {
    const ref = useRef(null);

    const functionThatUsesRef= () => {
        if (importedRefCaller(ref).thing==="Whatever") {
            doThing();
        };
    }

    return (<button ref={ref}>test</button>);
};

然后为了测试一个简单的:

const currentFile= require("path-to/current-file");

it("Should trigger do the thing", () => {
    let refMock = jest.spyOn(fileExplorer, "refCaller");
    refMock.mockImplementation((ref) => {
        return { thing: "Whatever" };
    });

然后在此之后的任何内容都将与模拟函数一起使用。

有关模拟函数的更多信息,我发现:https : //pawelgrzybek.com/mocking-functions-and-modules-with-jest/Jest 模拟内部函数很有帮助

如果您ref在组件的嵌套钩子中使用,并且您总是需要一个特定的current值,而不仅仅是第一个渲染器。您可以在测试中使用以下选项:

const reference = { current: null };
Object.defineProperty(reference, "current", {
    get: jest.fn(() => null),
    set: jest.fn(() => null),
});
const useReferenceSpy = jest.spyOn(React, "useRef").mockReturnValue(reference);

并且不要忘记useRef像下面这样在组件中

const ref = React.useRef(null)