1

the goal

I am trying to test that useEffect function that changes the text of a follow button to unfollow after he have been clicked

the result

when i test the component the follow/unfollow action is getting dispatched but the text of the button is not changing

explanation of the login inside the useEffect function

The way it hapeens is that after clicking the follow button a follow/unfollow action is dispatched and add the who got followed (userToFollow) to the logged user following accounts array (loggedUserData.following), and then changes the button text to unfollow and vice verca.

my 2 cents on why the test is failing

I suppose that the problem lays in the fact that i mocked the follow/unfollow action therefore it's just getting called and not resolved the way its should, if I could add the user that have been followed to the list of the logged user following accounts the problem should be solved.

anyone has an idea how can I do it or offer a better idea to solve it?

SOLUTION so the problem was that redux-mock-store doesnt approve updating state so I've rendered the tests with real the redux Store and everything works

load logged user data action

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 });
    }
};

the error

TestingLibraryElementError: Unable to find an accessible element with the role "button" and name "unfollow"

the test

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();
    });
});

the component

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;

the follow action

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 Answer 1

1

In this scenario it looks that you want to simply spy on the followUnFollowAction function and mock the axios library post method so it returns what your component expects:

In your test:

(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);

...

Additionally when testing for a state change I would recommend to use findBy instead of getBy as findBy returns a promise and will wait for the changes to get committed to the "DOM" (jsdom when testing) like:

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

...

const unFollowButton = await screen.findByRole('button', { name: 'unfollow' });
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the help, I've tried it yet still the same error arise. the mock didnt update the state data. the array loggedUserData.following is still empty
i have updated the question, added the action.tsx code if it can help you can check it out
Updated the solution to use a spy instead, so followUnFollowAction is not mocked
the followUnFollowAction is failing and enter the catch block instead of the updating the reducer state. is there any way to force the action to succeed? or updating the state manually inside the test should also do the trick
yes now that the real followUnFollowAction function is used, your axios.post request is failing, you'll need to mock the axios library so you can control what the axios.post returns, I updated my answer
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.