我的 React 應用程式的 App 組件中有一個函式,它在首次呈現時重繪 用戶的訪問令牌(useEffect 掛鉤)。目前,單元測驗正在檢查組件渲染結束時狀態的變化情況。如何使函式本身更可測驗?
我考慮過重構,將dispatch()鉤子、logout()reducer 和本地setLoading()狀態函式作為引數傳遞給函式,以便它們可以被模擬/因此函式可以從組件本身外部化,但我不確定這會帶來什么價值.
我知道 100% 的測驗覆寫率不是必需的,但我正在學習并希望在這樣做的同時盡我所能。
一點背景關系:
應用程式使用 ReduxToolkit 切片進行身份驗證狀態,包括當前已驗證用戶的用戶物件和訪問令牌,或來賓用戶的空值。
自動重繪 邏輯在自定義 fetchBaseQuery 中實作。
下面的代碼描述了為已登錄并在 localStorage 中具有重繪 令牌的用戶重繪 訪問令牌,但已重繪 頁面,清除 redux 狀態。它在渲染任何路由/視圖之前重繪 accessToken,以避免用戶每次重繪 頁面時都必須輸入憑據。
這是當前的實作:
//imports
...
const App = () => {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(true);
useEffect(() => {
const refresh = async () => {
const token = localStorage.getItem("refreshToken");
if (token) {
const refreshRequest = {
refresh: token,
};
const response = await fetch(
`${process.env.REACT_APP_API_URL}/auth/refresh/`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(refreshRequest),
}
);
if (response.status === 200) { // This branch gets no test coverage and I can't figure out how to fix that.
const data: RefreshResponse = await response.json();
// Should this be passed into the function to make it more reusable/testable?
dispatch(
setCredentials({ user: data.user, token: data.accessToken })
);
}
}
// Should this be passed into the function to make it more reusable/testable?
setLoading(false);
};
refresh();
}, [dispatch]);
if (loading) return (
<div className="h-100 d-flex justify-content-center align-items-center bg-dark">
<Spinner animation="border" />
</div>
);
return (
<>
<Routes>
// Routes
</Routes>
</>
);
}
export default App;
以下是相關的測驗用例:
it("should successfully request refresh access token on render", async () => {
// refresh() expects a refreshToken item in localStorage
localStorage.setItem("refreshToken", "testRefreshToken");
// I can't use enzyme because I'm on react 18, so no shallow rendering afaik :/
// renderWithProviders renders including a redux store with auth/api reducers
const { store } = renderWithProviders(
<MemoryRouter>
<App />
</MemoryRouter>
);
await waitFor(() => {
expect(store.getState().auth.token).toBe("testAccessToken");
});
localStorage.removeItem("refreshToken");
});
it("should fail to request refresh access token on render", async () => {
localStorage.setItem("refreshToken", "testRefreshToken");
// msn api route mocking, force a 401 error rather than the default HTTP 200 impl
server.use(
rest.post(
`${process.env.REACT_APP_API_URL}/auth/refresh/`,
(req, res, ctx) => {
return res(ctx.status(401));
}
)
);
const { store } = renderWithProviders(
<MemoryRouter>
<App />
</MemoryRouter>
);
await waitFor(() => {
expect(store.getState().auth.token).toBeNull();
});
localStorage.removeItem("refreshToken");
});
it("should not successfully request refresh access token on render", async () => {
const { store } = renderWithProviders(
<MemoryRouter>
<App />
</MemoryRouter>
);
await waitFor(() => {
expect(store.getState().auth.token).toBe(null);
});
});
uj5u.com熱心網友回復:
我的建議:
- 移動
dispatch,useState和useEffect到自定義鉤子。它看起來像:const useTockenRefresh() { // Name of the custom hook can be anything that is started from work 'use' const dispatch = useAppDispatch(); const [loading, setLoading] = useState(true); useEffect(() => { /* useEffect code as is */ }, [/* deps */]) return loading } export default useTockenRefresh - 在
useTockenRefresh您的組件中 使用const App = () => { const loading = useTockenRefresh() if (loading) return ( // And rest of your code }
現在可以單獨進行測驗useTockenRefresh。我建議為此目的使用React Hooks 測驗庫。由于這將是單元測驗,因此最好模擬外部的所有內容,例如useAppDispatch,fetch等。
import { renderHook, act } from '@testing-library/react-hooks'
// Path to useTockenRefresh should be correct relative to test file
// This mock mocks default export from useTockenRefresh
jest.mock('./useTockenRefresh', () => jest.fn())
// This mock for the case when useAppDispatch is exported as named export, like
// export const useAppDispatch = () => { ... }
jest.mock('./useAppDispatch', () => ({
useAppDispatch: jext.fn(),
}))
// If fetch is in external npm package
jest.mock('fetch', () => jest.fn())
jest.mock('./setCredentials', () => jest.fn())
// Mock other external actions/libraries here
it("should successfully request refresh access token on render", async () => {
// Mock dispatch. So we will not update real store, but see if dispatch has been called with right arguments
const dispatch = jest.fn()
useAppDispatch.mockReturnValueOnce(dispatch)
const json = jest.fn()
fetch.mockReturnValueOnce(new Promise(resolve => resolve({ status: 200, json, /* and other props */ })
json.mockReturnValueOnce(/* mock what json() should return */)
// Execute hook
await act(async () => {
const { rerender } = renderHook(() => useTockenRefresh())
return rerender()
})
// Check that mocked actions have been called
expect(fetch).toHaveBeenCalledWith(
`${process.env.REACT_APP_API_URL}/auth/refresh/`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(refreshRequest),
})
expect(setCredentials).toHaveBeenCalledWith(/* args of setCredentials from mocked responce object */
// And so on
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/460506.html
