单元测试访问全局存储的 React 组件
Unit testing React components that access a global store
首先,请让我知道这里是否适合提出此问题或将其移至正确的位置。
我开始为一些 React 组件编写单元测试。其中一个没有任何道具,但依赖于 store
:
const PlayerSelector = () => {
const classes = useStyles();
const divRef = useRef(null);
const [{ playerList }, dispatch] = useStore();
...
其中 userStore()
来自:
export const StateContext = createContext(null);
export const StoreProvider = ({ initialState, reducer, children }) => (
<StateContext.Provider
value={useReducer(reducer, initialState)}
>
{children}
</StateContext.Provider>
);
export const useStore = () => useContext(StateContext);
我的问题是,当我在测试文件中尝试 render
PlayerSelector
时,我 运行 出错了,因为测试无法访问该存储:
it('loads component', () => {
const { queryByRole } = render(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
结果是TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
指向这一行:
const [{ firmList }, dispatch] = useStore();
经过大量谷歌搜索后,我发现了一些有用的资源,指出了如何模拟上下文——但没有说明如何为 reducer 做同样的事情。我不知道我是否使事情过于复杂并且可能应该找到另一种测试方法,或者我是否遗漏了一个明显的答案。无论如何,如果您能分享任何提示或指导,我将不胜感激。
当您测试使用来自提供程序的上下文的组件时,您还需要呈现提供程序以提供上下文。 render
采用第二个选项参数,可以包含 wrapper 来包装被测试的组件。
示例:
const customWrapper = ({ children }) => <SomeProvider>{children}</SomeProvider>;
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: customWrapper
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
使用类似 redux 的提供程序,您可以为我们创建一个 StoreProvider
实例来创建用于测试的包装器。
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
),
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
如果您发现自己需要大量编写包装器实用程序,您还可以创建一个 custom render function,它或多或少与上述内容重复。
const StateProvider = ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
);
export const renderWithState = (ui, options) => {
return render(ui, { wrapper: StateProvider, ...options });
}
测试
import { renderWithState } from '../path/to/utils';
...
it('loads component', () => {
const { queryByRole } = renderWithState(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
首先,请让我知道这里是否适合提出此问题或将其移至正确的位置。
我开始为一些 React 组件编写单元测试。其中一个没有任何道具,但依赖于 store
:
const PlayerSelector = () => {
const classes = useStyles();
const divRef = useRef(null);
const [{ playerList }, dispatch] = useStore();
...
其中 userStore()
来自:
export const StateContext = createContext(null);
export const StoreProvider = ({ initialState, reducer, children }) => (
<StateContext.Provider
value={useReducer(reducer, initialState)}
>
{children}
</StateContext.Provider>
);
export const useStore = () => useContext(StateContext);
我的问题是,当我在测试文件中尝试 render
PlayerSelector
时,我 运行 出错了,因为测试无法访问该存储:
it('loads component', () => {
const { queryByRole } = render(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
结果是TypeError: object null is not iterable (cannot read property Symbol(Symbol.iterator))
指向这一行:
const [{ firmList }, dispatch] = useStore();
经过大量谷歌搜索后,我发现了一些有用的资源,指出了如何模拟上下文——但没有说明如何为 reducer 做同样的事情。我不知道我是否使事情过于复杂并且可能应该找到另一种测试方法,或者我是否遗漏了一个明显的答案。无论如何,如果您能分享任何提示或指导,我将不胜感激。
当您测试使用来自提供程序的上下文的组件时,您还需要呈现提供程序以提供上下文。 render
采用第二个选项参数,可以包含 wrapper 来包装被测试的组件。
示例:
const customWrapper = ({ children }) => <SomeProvider>{children}</SomeProvider>;
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: customWrapper
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
使用类似 redux 的提供程序,您可以为我们创建一个 StoreProvider
实例来创建用于测试的包装器。
it('loads component', () => {
const { queryByRole } = render(
<PlayerSelector />,
{
wrapper: ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
),
},
);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});
如果您发现自己需要大量编写包装器实用程序,您还可以创建一个 custom render function,它或多或少与上述内容重复。
const StateProvider = ({ children }) => (
<StoreProvider
initialState={{}} // <-- any initial state object
reducer={rootReducerFunction} // <-- any reducer function
>
{children}
</StoreProvider>
);
export const renderWithState = (ui, options) => {
return render(ui, { wrapper: StateProvider, ...options });
}
测试
import { renderWithState } from '../path/to/utils';
...
it('loads component', () => {
const { queryByRole } = renderWithState(<PlayerSelector />);
expect(queryByRole('button')).toHaveAttribute('aria-label');
});