在 React 测试库 + jest 中测试 useEffect
testing useEffect in react testing library + jest
目标
我正在尝试测试 useEffect 函数,该函数将关注按钮的文本更改为在点击他后取消关注
结果
当我测试组件时,follow/unfollow 动作正在被调度,但按钮的文本没有改变
useEffect函数内部登录说明
它发生的方式是在单击关注按钮后调度一个 follow/unfollow 操作并将被关注的人 (userToFollow) 添加到已记录的用户关注帐户数组 (loggedUserData.following),并且然后将按钮文本更改为取消关注,反之亦然。
关于测试失败原因的 2 美分
我想问题在于我模拟了 follow/unfollow 操作,因此它只是被调用而不是按应有的方式解决,如果我可以将已关注的用户添加到列表中已登录用户的以下帐户问题应该已解决。
有人知道我该怎么做或提供更好的解决方案吗?
解决方案
所以问题是 redux-mock-store 不批准更新状态所以我用真实的 redux Store 渲染了测试并且一切正常
加载记录的用户数据操作
export const loadloggedUserDataAction = () => async (dispatch) => {
try {
const config = {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Token ${localStorage.getItem('auth_token')}`,
},
};
const res = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/api/accounts/logged_user/`, config);
dispatch({ type: GET_LOGGED_USER_DETAILS_SUCCESS, payload: res.data
});
console.log(res.data);
/*
res.data = data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
*/
} catch (err) {
dispatch({ type: GET_LOGGED_USER_DETAILS_FAIL });
}
};
错误
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "unfollow"
测试
jest.mock('axios', () => ({
post: () =>
Promise.resolve({
data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
}),
get: () =>
Promise.resolve({
data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
}),
}));
const userToFollow = 'userId';
const loggedUserData = {
id: 'id',
name: 'name',
email: 'email',
following: [],
followers: [],
};
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const initialState = {
userReducer: { isUserAuthenticated: true, loggedUserData: loggedUserData },
};
const store = mockStore(initialState);
describe('FollowUnFollow - isUserAlreadyFollowed false', () => {
beforeEach(() => {
render(
<Provider store={store}>
<FollowUnFollow userToFollow={userToFollow} />
</Provider>
);
});
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
test('follow button should dispatch followUnFollowAction ',async () => {
const followButton = screen.getByRole('button', { name: 'follow' });
userEvent.click(followButton);
const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });
expect(unFollowButton).toBeInTheDocument();
});
});
组件
const FollowUnFollow = ({ userToFollow }) => {
const dispatch = useDispatch();
const [button, setButton] = useState('follow');
const { loggedUserData, isUserAuthenticated } = useSelector((state) => state.userReducer);
console.log(loggedUserData)
/*
loggedUserData ===
{
id: 'id',
name: 'name',
email: 'email',
following: [],
followers: [],
}
*/
useEffect(() => {
try {
const isUserAlreadyFollowed = loggedUserData?.following.includes(userToFollow);
if (isUserAlreadyFollowed) {
setButton('unfollow');
} else {
setButton('follow');
}
} catch {}
}, [dispatch, loggedUserData, userToFollow]);
const onSubmit = (e) => {
e.preventDefault();
try {
dispatch(followUnFollowAction({ user_to_follow: userToFollow }));
} catch {}
};
const authLinks = (
<form onSubmit={(e) => onSubmit(e)}>
<button>{button}</button>
</form>
);
return <div data-testid='followUnFollow'>{isUserAuthenticated ? <div>{authLinks}</div> : null}</div>;
};
export default FollowUnFollow;
后续动作
export const followUnFollowAction =
({ user_to_follow }) =>
async (dispatch) => {
try {
const config = {
headers: {
'content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Token ${localStorage.getItem('auth_token')}`,
},
};
const body = JSON.stringify({ user_to_follow });
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/api/followers/follow/`, body, config);
dispatch({ type: FOLLOW_UNFOLLOW_USER_SUCCESS, payload: res.data });
dispatch(loadUserDetailsAction({ id: user_to_follow }));
dispatch(loadloggedUserDataAction());
} catch {
dispatch({ type: FOLLOW_UNFOLLOW_USER_FAIL });
}
};
在这种情况下,您似乎只想监视 followUnFollowAction
函数并模拟 axios 库 post 方法,因此它 returns 您的组件所期望的:
在你的测试中:
(delete the mock for `jest.mock('../../../redux/actions/follower')`)
import * as follower from '../../../redux/actions/follower';
jest.mock('axios', () => ({
default: {
post: () => Promise.resolve({
data: *data that you want to send to the FOLLOW_UNFOLLOW_USER_SUCCESS action*
})
}
}))
...
test('follow button should dispatch followUnFollowAction ', () => {
const followUnFollowActionSpy = jest.spy(follower, 'followUnFollowAction');
...
const timesActionHaveDispatched = followUnFollowActionSpy.mock.calls.length;
expect(timesActionHaveDispatched).toBe(1);
expect(followUnFollowActionSpy.mock.calls[0][0].user_to_follow).toBe(userToFollow);
...
此外,在测试状态更改时,我建议使用 findBy
而不是 getBy
作为 findBy
returns 承诺,并将等待更改得到致力于“DOM”(测试时的jsdom),如:
test('follow button should dispatch followUnFollowAction ', async () => {
...
const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });
目标
我正在尝试测试 useEffect 函数,该函数将关注按钮的文本更改为在点击他后取消关注
结果
当我测试组件时,follow/unfollow 动作正在被调度,但按钮的文本没有改变
useEffect函数内部登录说明
它发生的方式是在单击关注按钮后调度一个 follow/unfollow 操作并将被关注的人 (userToFollow) 添加到已记录的用户关注帐户数组 (loggedUserData.following),并且然后将按钮文本更改为取消关注,反之亦然。
关于测试失败原因的 2 美分
我想问题在于我模拟了 follow/unfollow 操作,因此它只是被调用而不是按应有的方式解决,如果我可以将已关注的用户添加到列表中已登录用户的以下帐户问题应该已解决。
有人知道我该怎么做或提供更好的解决方案吗?
解决方案 所以问题是 redux-mock-store 不批准更新状态所以我用真实的 redux Store 渲染了测试并且一切正常
加载记录的用户数据操作
export const loadloggedUserDataAction = () => async (dispatch) => {
try {
const config = {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Token ${localStorage.getItem('auth_token')}`,
},
};
const res = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/api/accounts/logged_user/`, config);
dispatch({ type: GET_LOGGED_USER_DETAILS_SUCCESS, payload: res.data
});
console.log(res.data);
/*
res.data = data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
*/
} catch (err) {
dispatch({ type: GET_LOGGED_USER_DETAILS_FAIL });
}
};
错误
TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "unfollow"
测试
jest.mock('axios', () => ({
post: () =>
Promise.resolve({
data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
}),
get: () =>
Promise.resolve({
data: {
id: 'id',
name: 'name',
email: 'email',
following: [userToFollow],
followers: [userToFollow],
},
}),
}));
const userToFollow = 'userId';
const loggedUserData = {
id: 'id',
name: 'name',
email: 'email',
following: [],
followers: [],
};
const middlewares = [thunk];
const mockStore = configureStore(middlewares);
const initialState = {
userReducer: { isUserAuthenticated: true, loggedUserData: loggedUserData },
};
const store = mockStore(initialState);
describe('FollowUnFollow - isUserAlreadyFollowed false', () => {
beforeEach(() => {
render(
<Provider store={store}>
<FollowUnFollow userToFollow={userToFollow} />
</Provider>
);
});
afterEach(() => {
cleanup();
jest.clearAllMocks();
});
test('follow button should dispatch followUnFollowAction ',async () => {
const followButton = screen.getByRole('button', { name: 'follow' });
userEvent.click(followButton);
const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });
expect(unFollowButton).toBeInTheDocument();
});
});
组件
const FollowUnFollow = ({ userToFollow }) => {
const dispatch = useDispatch();
const [button, setButton] = useState('follow');
const { loggedUserData, isUserAuthenticated } = useSelector((state) => state.userReducer);
console.log(loggedUserData)
/*
loggedUserData ===
{
id: 'id',
name: 'name',
email: 'email',
following: [],
followers: [],
}
*/
useEffect(() => {
try {
const isUserAlreadyFollowed = loggedUserData?.following.includes(userToFollow);
if (isUserAlreadyFollowed) {
setButton('unfollow');
} else {
setButton('follow');
}
} catch {}
}, [dispatch, loggedUserData, userToFollow]);
const onSubmit = (e) => {
e.preventDefault();
try {
dispatch(followUnFollowAction({ user_to_follow: userToFollow }));
} catch {}
};
const authLinks = (
<form onSubmit={(e) => onSubmit(e)}>
<button>{button}</button>
</form>
);
return <div data-testid='followUnFollow'>{isUserAuthenticated ? <div>{authLinks}</div> : null}</div>;
};
export default FollowUnFollow;
后续动作
export const followUnFollowAction =
({ user_to_follow }) =>
async (dispatch) => {
try {
const config = {
headers: {
'content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Token ${localStorage.getItem('auth_token')}`,
},
};
const body = JSON.stringify({ user_to_follow });
const res = await axios.post(`${process.env.NEXT_PUBLIC_API_URL}/api/followers/follow/`, body, config);
dispatch({ type: FOLLOW_UNFOLLOW_USER_SUCCESS, payload: res.data });
dispatch(loadUserDetailsAction({ id: user_to_follow }));
dispatch(loadloggedUserDataAction());
} catch {
dispatch({ type: FOLLOW_UNFOLLOW_USER_FAIL });
}
};
在这种情况下,您似乎只想监视 followUnFollowAction
函数并模拟 axios 库 post 方法,因此它 returns 您的组件所期望的:
在你的测试中:
(delete the mock for `jest.mock('../../../redux/actions/follower')`)
import * as follower from '../../../redux/actions/follower';
jest.mock('axios', () => ({
default: {
post: () => Promise.resolve({
data: *data that you want to send to the FOLLOW_UNFOLLOW_USER_SUCCESS action*
})
}
}))
...
test('follow button should dispatch followUnFollowAction ', () => {
const followUnFollowActionSpy = jest.spy(follower, 'followUnFollowAction');
...
const timesActionHaveDispatched = followUnFollowActionSpy.mock.calls.length;
expect(timesActionHaveDispatched).toBe(1);
expect(followUnFollowActionSpy.mock.calls[0][0].user_to_follow).toBe(userToFollow);
...
此外,在测试状态更改时,我建议使用 findBy
而不是 getBy
作为 findBy
returns 承诺,并将等待更改得到致力于“DOM”(测试时的jsdom),如:
test('follow button should dispatch followUnFollowAction ', async () => {
...
const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });