我有一个使用useContext的组件,它的输出依赖于上下文中的值。
useContext
import React, { useContext } from 'react'; const MyComponent = () => { const name = useContext(NameContext); return <div>{name}</div>; };
当使用react和jest快照中的shallow renderer测试这个组件时,如何更改NameContext的值?
NameContext
vs91vp4v1#
一般来说,使用钩子不会改变测试策略,更大的问题实际上不是钩子,而是上下文的使用,这会使事情变得有点复杂。有很多方法可以实现这一点,但我发现的唯一一种适用于'react-test-renderer/shallow'的方法是注入一个mock钩子:
'react-test-renderer/shallow'
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'); });
不过,这有点脏,并且是特定于实现的,因此如果您能够在使用浅渲染器方面做出妥协,还有一些其他选项可用:
如果不是浅呈现,可以将组件 Package 在上下文提供器中以注入所需的值:
import TestRenderer from 'react-test-renderer'; test("non-shallow render", () => { const element = new TestRenderer.create( <NameContext.Provider value="Provided Value"> <MyComponent /> </NameContext.Provider> ); expect(element.root.findByType("div").children).toEqual(['Provided Value']); });
(免责声明:这 * 应该 * 工作,但当我测试它,我击中了一个错误,我认为这是一个问题,在我的设置)
正如@skyboyer评论的那样,enzyme的浅层渲染器支持.dive,允许你对浅层渲染组件的一部分进行深层渲染:
.dive
import { shallow } from "./enzyme"; test("enzyme dive", () => { const TestComponent = () => ( <NameContext.Provider value="Provided Value"> <MyComponent /> </NameContext.Provider> ); const element = shallow(<TestComponent />); expect(element.find(MyComponent).dive().text()).toBe("Provided Value"); });
最后,Hooks FAQ有一个使用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"); });
gijlo24d2#
我尝试使用Enzyme + .dive,但是当潜水时,它不能识别上下文属性,它得到默认的属性。实际上,这是Enzyme团队已知的问题。同时,我想出了一个更简单的解决方案,包括创建一个自定义钩子来返回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> ); }
Hello.js -使用上下文
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;
js-使用Enzyme shallow测试useContext
import React from 'react'; import { shallow } from 'enzyme'; import * as AppContext from './AppContext'; import Hello from './Hello'; describe('<Hello />', () => { test('it should mock the context', () => { const contextValues = { color: 'orange' }; jest .spyOn(AppContext, 'useAppContext') .mockImplementation(() => contextValues); const wrapper = shallow(<Hello />); const h1 = wrapper.find('h1'); expect(h1.text()).toBe('Hello orange!'); }); });
检出完整的中型文章https://medium.com/7shifts-engineering-blog/testing-usecontext-react-hook-with-enzyme-shallow-da062140fc83
b4lqfgs43#
或者,如果您正在单独测试组件,而没有挂载父组件,您可以简单地模拟useContext:
jest.mock('react', () => { const ActualReact = jest.requireActual('react') return { ...ActualReact, useContext: () => ({ }), // what you want to return when useContext get fired goes here } })
eblbsuwk4#
为了完成上面公认的答案,对于非浅呈现,我稍微调整了代码,使其简单地用上下文包围组件
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...; });
cunj1qz15#
旧职位,但如果它帮助某人,这就是我如何得到它的工作
import * as React from 'react'; import { shallow } from 'enzyme'; describe('MyComponent', () => { it('should useContext mock and shallow render a div tag', () => { jest.spyOn(React, 'useContext').mockImplementation(() => ({ name: 'this is a mock context return value' })); const myComponent = shallow( <MyComponent props={props} />).dive(); expect(myComponent).toMatchSnapShot(); }); });
8aqjt8rx6#
在测试中,您需要用“上下文提供者” Package 组件。下面是一个简单的例子。DisplayInfo组件依赖于用户上下文。
import React, { useContext } from 'react'; import { UserContext } from './contexts/UserContextProvider'; export const DisplayInfo = () => { const { userInfo } = useContext(UserContext); const dispUserInfo = () => { return userInfo.map((user, i) => { return ( <div key={i}> <h1> Name: { user.name } </h1> <h1> Email: { user.email } </h1> </div> ) }); } return( <> <h1 data-testid="user-info"> USER INFORMATION </h1> { userInfo && dispUserInfo() }) </> } export default DisplayInfo;
这是用户上下文提供程序。
import React, {useState, createContext} from 'react'; export const UserContext = createContex(); const UserContextProvider = () => { const [userInfo, setUserInfo] = useState([]); const updateUserInfo = () => { setUserInfo([...userInfo, newData]); } const values = { userInfo, updateUserInfo } return( <UserContext.Provider = vlaue={values}> {props.children} </UserContext.Provider> ) } export default UserContextProvider;
要测试“显示信息”组件,可能还需要使用“react-router-dom”中的“MemoryRouter”。示例如下-
import React from "react"; import { render, screen } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; import { DisplayInfo } from "./DisplayInfo"; import UserContextProvider from "./contexts/UserContextProvider"; import { MemoryRouter } from "react-router-dom"; describe("DisplayInfo", () => { describe("layout", () => { it("has header of user info", () => { render( <UserContextProvider> <DisplayInfo /> </UserContextProvider>, { wrapper: MemoryRouter } ); let header = screen.getByTestId('user-info'); expect(header).toHaveTextContent(/user information/i) }); }); });
fgw7neuy7#
我所做的是测试是否使用了useContext。在我的例子中,useContext返回名为dispatch的函数。在组件中,我有:
dispatch
const dispatch = useContext(...);
然后在onChange方法中:
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;
然后:
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); });
fbcarpbf8#
我已经找到了一种很好的方法来模拟上下文,在那里我可以优雅地注入自己的值进行测试。
import React, { createContext, useContext, useState, useEffect, } from "react"; const initState = { data: [], }; export const ThingsContext = createContext(initState); export const useThings = () => { const data = useContext(ThingsContext); if (data === undefined) { throw new Error("useThing must be used within a ThingsProvider"); } return data; }; export const ThingsProvider = ({ children, value: { state: oState } = {}, }) => { const[data, setData] = useState(); useEffect(() => { const getData = async () => { const data = await // api call; setData(data); } getData(); }, []); return ( <ThingsContext.Provider value={{ state: oState ?? state }} > {children} </ThingsContext.Provider> ); };
这样,我就可以覆盖实际状态并测试我想要的任何值。
import React from "react"; import { render, screen } from "@testing-library/react"; import { ThingsProvider } from "store/things"; import Things from "./Things"; const defaultValue = { state: ["pot", "kettle", "black"], }; const renderComponent = (children, value=defaultValue) => { const el = render( <ThingsProvider value={value ?? defaultValue} >{children}</ThingsProvider> ); screen.debug(); return el; }; describe("<Things />", () => { it("should render thing", () => { renderComponent(<Things />); }); expect(screen.getByText("pot")).toBeInTheDocument(); it("should not render thing", () => { renderComponent(<Things />, {state: []}); }); expect(screen.queryByText("pot")).not.toBeInTheDocument(); });
8条答案
按热度按时间vs91vp4v1#
一般来说,使用钩子不会改变测试策略,更大的问题实际上不是钩子,而是上下文的使用,这会使事情变得有点复杂。
有很多方法可以实现这一点,但我发现的唯一一种适用于
'react-test-renderer/shallow'
的方法是注入一个mock钩子:不过,这有点脏,并且是特定于实现的,因此如果您能够在使用浅渲染器方面做出妥协,还有一些其他选项可用:
非浅渲染
如果不是浅呈现,可以将组件 Package 在上下文提供器中以注入所需的值:
(免责声明:这 * 应该 * 工作,但当我测试它,我击中了一个错误,我认为这是一个问题,在我的设置)
使用酶和潜水进行浅渲染
正如@skyboyer评论的那样,enzyme的浅层渲染器支持
.dive
,允许你对浅层渲染组件的一部分进行深层渲染:使用ReactDOM
最后,Hooks FAQ有一个使用
ReactDOM
测试钩子的例子,它也能正常工作。自然,使用ReactDOM
意味着这也是一个深度渲染,而不是浅度渲染。gijlo24d2#
我尝试使用Enzyme +
.dive
,但是当潜水时,它不能识别上下文属性,它得到默认的属性。实际上,这是Enzyme团队已知的问题。同时,我想出了一个更简单的解决方案,包括创建一个自定义钩子来返回useContext
和您的上下文,并在测试中模拟这个自定义钩子的返回:AppContext.js -创建上下文。
App.js - 提供上下文
Hello.js -使用上下文
js-使用Enzyme shallow测试useContext
检出完整的中型文章https://medium.com/7shifts-engineering-blog/testing-usecontext-react-hook-with-enzyme-shallow-da062140fc83
b4lqfgs43#
或者,如果您正在单独测试组件,而没有挂载父组件,您可以简单地模拟useContext:
eblbsuwk4#
为了完成上面公认的答案,对于非浅呈现,我稍微调整了代码,使其简单地用上下文包围组件
cunj1qz15#
旧职位,但如果它帮助某人,这就是我如何得到它的工作
8aqjt8rx6#
在测试中,您需要用“上下文提供者” Package 组件。下面是一个简单的例子。
DisplayInfo组件依赖于用户上下文。
这是用户上下文提供程序。
要测试“显示信息”组件,可能还需要使用“react-router-dom”中的“MemoryRouter”。示例如下-
fgw7neuy7#
我所做的是测试是否使用了
useContext
。在我的例子中,useContext
返回名为dispatch
的函数。在组件中,我有:
然后在
onChange
方法中:所以开始时的内部测试:
然后:
fbcarpbf8#
我已经找到了一种很好的方法来模拟上下文,在那里我可以优雅地注入自己的值进行测试。
这样,我就可以覆盖实际状态并测试我想要的任何值。