在反应测试库中测试 useEffect + jest

时间:2021-08-01 16:01:55

标签: reactjs jestjs react-testing-library

目标

我正在尝试测试 useEffect 函数,该函数在点击后将关注按钮的文本更改为取消关注

结果

当我测试组件时,关注/取消关注动作被调度,但按钮的文本没有改变

useEffect函数内部登录说明

它的实现方式是,在单击关注按钮后,会调度关注/取消关注操作,并将关注的人 (userToFollow) 添加到已登录用户关注帐户数组 (loggedUserData.following),然后将按钮文本更改为取消关注,反之亦然。

我对测试失败原因的 2 美分

我认为问题在于我模拟了关注/取消关注操作,因此它只是被调用,并没有按照应有的方式解决,如果我可以将已关注的用户添加到已登录用户的列表中关注账号,问题就解决了。

有人知道我该怎么做或提供更好的想法来解决吗?

解决方案 所以问题是 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 });
        }
    };

1 个答案:

答案 0 :(得分:1)

在这种情况下,您似乎只想监视 followUnFollowAction 函数并模拟 axios 库 post 方法,以便它返回您的组件期望的内容:

在您的测试中:

(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 返回承诺并等待更改提交到“DOM” (测试时使用 jsdom)如:

test('follow button should dispatch followUnFollowAction ', async () => {

...

const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });