那里!
我这里有一个useFetch,它将使用useReducer两次触发组件的状态更改
type AsyncType = 'INIT' | 'PENDING' | 'RESOLVED' | 'REJECTED' | 'REDIRECTED'
type AsyncAction =
| { type: 'PENDING' }
| { type: 'RESOLVED'; data: any }
| { type: 'REJECTED'; error: Error }
| { type: 'REDIRECTED' }
interface AsyncState {
data?: any
error?: Error
type: AsyncType
}
const dataFetchReducer = (
state: AsyncState,
action: AsyncAction
): AsyncState => {
switch (action.type) {
case 'PENDING':
return {
...state,
type: 'PENDING',
}
case 'RESOLVED':
return {
...state,
type: 'RESOLVED',
data: action.data,
}
// We can choose to ignore it, retry it or throw it to let the error boundary to catch it.
case 'REJECTED':
return {
...state,
type: 'REJECTED',
error: action.error,
}
case 'REDIRECTED':
return {
...state,
type: 'REDIRECTED',
}
default:
throw new Error()
}
}
// We can ignore the input if we don't want it to fetch new data when the component just mounted
export const useFetch = (
initialRequestConfig?: AxiosRequestConfig,
initialData?: any
): [AsyncState, Dispatch<AxiosRequestConfig>] => {
const [requestConfig, setRequestConfig] = useState(initialRequestConfig)
const [state, dispatch] = useReducer<typeof dataFetchReducer>(
dataFetchReducer,
{
type: 'INIT',
data: initialData,
}
)
useEffect(() => {
if (!requestConfig) return
let didCancel = false
const fetchData = async () => {
dispatch({ type: 'PENDING' })
try {
const result = await axios(requestConfig)
if (!didCancel) {
dispatch({ type: 'RESOLVED', data: result.data })
}
} catch (error) {
if (!didCancel) {
if (
error.response &&
error.response.data &&
error.response.data.redirect
) {
dispatch({ type: 'REDIRECTED' })
} else {
dispatch({ type: 'REJECTED', error })
}
}
}
}
fetchData()
return () => {
didCancel = true
}
}, [requestConfig])
return [state, setRequestConfig]
}
但是,我发现为使用它的任何组件编写单元测试都非常困难。例如,我们有这样的组件
export const PrivateRoute: FC<RouteProps> = ({
component: Component,
...rest
}) => {
const [state] = useFetch(api.getUser()) // the api with only return the axios config, like this: { method: "GET", url: '/user' }
if (!Component) return null
console.log(state)
return (
<Route
{...rest}
render={props =>
state.type === 'PENDING' ? (
<p>Loading</p>
) : state.type === 'RESOLVED' ? (
<Component {...props} />
) : state.type === 'REJECTED' ? (
<Error err={state.error} />
) : null
}
/>
)
}
当我尝试测试它时。无论我做什么。我不能让它呈现<Component />
而不是<div>Loading</div>
.
我只是这样嘲笑公理
import axios, { AxiosStatic } from 'axios'
interface AxiosMock extends AxiosStatic {
mockResolvedValue: Function
mockRejectedValue: Function
}
jest.mock('axios')
const mockedAxios = axios as AxiosMock
我尝试像这样测试我的组件
it('renders without crashing', async () => {
const MockComp = () => <p>Test</p>
mockedAxios.mockResolvedValue({ data: { user: 'caso' } })
let wrapper
await act(() => {
wrapper = mount(
<MemoryRouter initialEntries={['/random']}>
<PrivateRoute path="/" component={MockComp} />
</MemoryRouter>
)
wrapper.update() // wherever I put the update, the wrapper is always loading
})
console.log(wrapper.debug()) // this line will always be loading
expect(wrapper.find(Route).prop('path')).toBe('/')
})
总会有这样的警告Warning: An update to PrivateRoute inside a test was not wrapped in act(...).
我不知道测试它的正确方法是什么。我花了2天时间。有谁知道测试它的正确方法是什么?我已经升级到反应 16.9
我刚刚找到了最好的解决方案,我们可以使用 jest 来模拟useFetch
相反,我们可以简单地返回模拟状态并测试渲染结果。