Reactjs 如何测试依赖于useContext钩子的react组件?

Reactjs 如何测试依赖于useContext钩子的react组件?,reactjs,jestjs,react-hooks,react-test-renderer,Reactjs,Jestjs,React Hooks,React Test Renderer,我有一个使用useContext的组件,然后它的输出取决于上下文中的值。一个简单的例子: import React, { useContext } from 'react'; const MyComponent = () => { const name = useContext(NameContext); return <div>{name}</div>; }; import React,{useContext}来自“React”; 常量MyCompo

我有一个使用
useContext
的组件,然后它的输出取决于上下文中的值。一个简单的例子:

import React, { useContext } from 'react';

const MyComponent = () => {
  const name = useContext(NameContext);

  return <div>{name}</div>;
};
import React,{useContext}来自“React”;
常量MyComponent=()=>{
const name=useContext(NameContext);
返回{name};
};

使用react和jest快照中的浅层渲染器测试此组件时。如何更改
NameContext
的值?

一般来说,使用钩子不会对测试策略有太大的改变。这里更大的问题实际上不是钩子,而是上下文的使用,这使事情变得复杂了一点

有很多方法可以实现这一点,但我发现使用
'react-test-renderer/shall'
的唯一方法是注入一个模拟钩子:

import ShallowRenderer from 'react-test-renderer/shallow';

let realUseContext;
let useContextMock;
// Setup mock
beforeEach(() => {
    realUseContext = React.useContext;
    useContextMock = React.useContext = jest.fn();
});
// Cleanup mock
afterEach(() => {
    React.useContext = realUseContext;
});

test("mock hook", () => {
    useContextMock.mockReturnValue("Test Value");
    const element = new ShallowRenderer().render(
        <MyComponent />
    );
    expect(element.props.children).toBe('Test Value');
});
使用ReactDOM 最后,有一个使用
ReactDOM
测试钩子的示例,它也可以工作。当然,使用
ReactDOM
意味着这也是一种深度渲染,而不是浅渲染

let container;
beforeEach(() => {
    container = document.createElement('div');
    document.body.appendChild(container);
});

afterEach(() => {
    document.body.removeChild(container);
    container = null;
});

test("with ReactDOM", () => {
    act(() => {
        ReactDOM.render((
            <NameContext.Provider value="Provided Value">
                <MyComponent />
            </NameContext.Provider>
        ), container);
    });

    expect(container.textContent).toBe("Provided Value");
});
let容器;
在每个之前(()=>{
container=document.createElement('div');
文件.正文.附件(容器);
});
之后(()=>{
文件.正文.删除文件(容器);
container=null;
});
测试(“使用ReactDOM)”,()=>{
行动(()=>{
ReactDOM.render((
),货柜);
});
expect(container.textContent).toBe(“提供的值”);
});

我尝试使用Enzyme+
.dive
,但潜水时,它无法识别上下文道具,而是获得默认道具。事实上,这是酶研究小组已知的问题。 同时,我提出了一个更简单的解决方案,其中包括创建一个自定义钩子,只返回
useContext
,并在测试中模拟该自定义钩子的返回:

AppContext.js-创建上下文

import React, { useContext } from 'react';

export const useAppContext = () => useContext(AppContext);

const defaultValues = { color: 'green' };
const AppContext = React.createContext(defaultValues);

export default AppContext;
App.js — 提供背景

import React from 'react';
import AppContext from './AppContext';
import Hello from './Hello';

export default function App() {
  return (
    <AppContext.Provider value={{ color: 'red' }}>
      <Hello />
    </AppContext.Provider>
  );
}
import React from 'react';
import { useAppContext } from './AppContext';

const Hello = props => {
  const { color } = useAppContext();
  return <h1 style={{ color: color }}>Hello {color}!</h1>;
};

export default Hello;
import { mount } from 'enzyme';
import NameContext from './NameContext';

test("non-shallow render", () => {
    const dummyValue = {
      name: 'abcd',
      customizeName: jest.fn(),
      ...
    }; 
    const wrapper = mount(
        <NameContext.Provider value={dummyValue}>
            <MyComponent />
        </NameContext.Provider>
    );

    // then use  
    wrapper.find('...').simulate('change', ...);
    ...
    expect(wrapper.find('...')).to...;
});
从“React”导入React;
从“./AppContext”导入AppContext;
从“./Hello”导入Hello;
导出默认函数App(){
返回(

或者,如果您在隔离测试组件时没有安装父组件,您可以简单地模拟useContext:

jest.mock('react',()=>{
const ActualReact=jest.requireActual('react'))
返回{
…实际反应,
useContext:()=>({}),//激发useContext时要返回的内容将转到此处
}
})

您仍然可以使用动态
useContext
值和全局
jest.fn

来完成上述可接受的答案,对于非浅层渲染,我稍微调整了代码,使其仅用上下文包围我的组件

import React from 'react';
import AppContext from './AppContext';
import Hello from './Hello';

export default function App() {
  return (
    <AppContext.Provider value={{ color: 'red' }}>
      <Hello />
    </AppContext.Provider>
  );
}
import React from 'react';
import { useAppContext } from './AppContext';

const Hello = props => {
  const { color } = useAppContext();
  return <h1 style={{ color: color }}>Hello {color}!</h1>;
};

export default Hello;
import { mount } from 'enzyme';
import NameContext from './NameContext';

test("non-shallow render", () => {
    const dummyValue = {
      name: 'abcd',
      customizeName: jest.fn(),
      ...
    }; 
    const wrapper = mount(
        <NameContext.Provider value={dummyValue}>
            <MyComponent />
        </NameContext.Provider>
    );

    // then use  
    wrapper.find('...').simulate('change', ...);
    ...
    expect(wrapper.find('...')).to...;
});
从“酶”导入{mount};
从“./NameContext”导入NameContext;
测试(“非浅层渲染”,()=>{
常量dummyValue={
名称:“abcd”,
customizeName:jest.fn(),
...
}; 
常量包装器=装入(
);
//然后使用
find(“…”).simulate('change',…);
...
期望(wrapper.find(“…”).to。。。;
});

以前的帖子,但如果它对某人有帮助,我就是这样让它发挥作用的

import*as React from'React';
从“酶”导入{shall};
描述('MyComponent',()=>{
它('应该使用上下文模拟和浅层渲染div标记',()=>{
jest.spyOn(React,'useContext').mockImplementation(()=>({
名称:“这是一个模拟上下文返回值”
}));
常量myComponent=浅(
).潜水();
expect(myComponent.toMatchSnapShot();
});
});

我做的是测试是否使用了
useContext
。在我的例子中,
useContext
返回名为
dispatch
的函数

在组件中,我有:

const dispatch = useContext(...);
然后在
onChange
方法中:

dispatch({ type: 'edit', payload: { value: e.target.value, name: e.target.name } });

所以一开始的内部测试:

  const dispatch = jest.fn();
  React.useContext = (() => dispatch) as <T>(context: React.Context<T>) => T;

你可以用
包装你的组件:这似乎不适合浅层渲染,因为这样它就不会渲染我的组件的内部。当我尝试这样做时,我得到了这样的快照:
而不是
Paul
是的,这里是为
.dive()制作的
来自enzym,但react-shallow渲染器没有类似的功能。是的,你是对的,错过了模拟的想法。我没有想到这一点。没有其他方法那么优雅,但比我尝试的方法更优雅。我喜欢
deep
函数的想法,我希望他们能在s将其引入react-shallow渲染器有一点。谢谢你的回答!我在使用Ezyme时面临着一个挑战,我完全遵循了你展示的模拟想法,但问题是当我检查我的条件组件是否使用上下文值呈现时。注意:我使用useEffect中的上下文值更新我的组件。我遵循了相同的示例。我看到实际的默认值是moc每次我运行测试时都会被忽略。模拟值未渲染,请告诉我缺少什么。接受的答案包含非浅层渲染的示例。如果您仔细查看,它不完全相同(使用TestRenderer)当我尝试它时,给了我一个错误。因此我共享了它。我实现了它,但它对我不起作用。
包装器
返回为未定义=/Warning:它到处替换
useContext
,因此破坏了测试中也使用
useContext
的任何部分,例如样式化组件。Alex Andrade的回答是我将允许您分离出您想要模仿的
useContext
的哪些用法。@Chris您到底听说过jest吗?@Chris您可以检查
值。displayName
查看它是否来自
StylesContext
如果您有
useContext:value=>
,并相应返回或保留原始函数。
  it('calls function when change address input', () => {
   const input = component.find('[name="address"]');
   input.simulate('change', { target: { value: '123', name: 'address' } });

   expect(dispatch).toHaveBeenCalledTimes(1);
  });