我想测试一个自定义钩子,它是作为与其他钩子重用代码的帮助函数实现的。它在实现中调用useDispatch
和useSelector
,同时将数据保存在会话存储中:
export function useCustomHook(key, obj)
{
let myObject = {
somefield: obj.someField
};
sessionStorage.setItem(key, JSON.stringify(myObject));
const dispatch = useDispatch();
dispatch(actionCreator.addAction(key, myObject));
}
和测试:
it('should have data in redux and session storage', () =>
{
const obj = {
somefield: 'my val',
};
renderHook(() => useCustomHook('some key', obj));
let savedObj= JSON.parse(sessionStorage.getItem('some key'));
expect(savedObj.someField).toBe('my val');
renderHook(() =>
{
console.log("INSIDE");
let reduxObj = useSelector(state => state.vals);
console.log("THE OBJECT: " );
console.log(reduxObj);
expect(reduxObj).toBe(2); //just to see if it fails the test - it's not
});
})
无论我尝试什么,测试都只会到达"INSIDE"控制台日志,而不会打印"THE OBJECT:"控制台日志。测试本身仍然通过,所以就像useSelector
以某种方式停止了 renderHook 执行的其余部分。
我猜这与测试没有连接商店的事实有关......在这种情况下,可以做些什么来测试 redux?
您需要提供一个wrapper
组件,该组件添加 reduxProvider
以及要连接到的store
:
it('should have data in redux and session storage', () =>
{
const obj = {
somefield: 'my val',
};
const store = {} // create/mock a store
const wrapper = ({ children }) => <Provider store={store}>{children}</Provider>
renderHook(() => useCustomHook('some key', obj), { wrapper });
let savedObj= JSON.parse(sessionStorage.getItem('some key'));
expect(savedObj.someField).toBe('my val');
renderHook(() => {
console.log("INSIDE");
let reduxObj = useSelector(state => state.vals);
console.log("THE OBJECT: " );
console.log(reduxObj);
expect(reduxObj).toBe(2); //just to see if it fails the test - it's not
}, { wrapper });
})
作为旁注,renderHook
正在捕获错误,这就是为什么您在测试中看不到它们的原因,如果您尝试访问它会抛出result.current
并且您可以看到它以result.error
表示,但是这里的用法不从要断言的自定义钩子返回值是非常不寻常的。
这也可能会在第二个renderHook
调用中出现断言而导致问题。 您可能希望从钩子返回值并在外部断言,或者在 redux 存储中断言更新的值。
您可以编写记录的 reduxrenderWithProviders
函数的简单扩展。这看起来像:
import React, { PropsWithChildren } from 'react'
import { render } from '@testing-library/react'
import type { RenderOptions } from '@testing-library/react'
import { configureStore } from '@reduxjs/toolkit'
import type { PreloadedState } from '@reduxjs/toolkit'
import { Provider } from 'react-redux';
import { renderHook } from '@testing-library/react';
export function renderHookWithProviders<
Result,
Props>(
render: (initialProps: Props) => Result,
{
preloadedState = {},
// Automatically create a store instance if no store was passed in
store = configureStore({
reducer: {
....
},
preloadedState
}),
...renderOptions
}: ExtendedRenderOptions = {}
) {
function Wrapper({ children }: PropsWithChildren<{}>): JSX.Element {
return <Provider store={store}>{children}</Provider>
}
return { store, ...renderHook(render, { wrapper: Wrapper, ...renderOptions }) }
}
请注意,render
方法已替换为@testing-library/react
中的renderHook
。然后可以简单地在测试中使用:
describe('useGetRelated', () => {
test('Return related', () => {
const { result } = renderHookWithProviders(() => useGetRelated(), {
preloadedState: {
//initial state of redux
}
});
});
});
钩子测试库也简要地讨论了这个问题。尽管每个测试中的解决方案需要更多的样板,但 redux 倡导的解决方案似乎更好 IMO。