Monday, 6 December 2021

Redux store not being populated when mocking API call

I wrote a Register component in react, it is a simple form that on submit will post to an API. The call to the API will return an object with certain data, this data will be then added to the redux store.

I wrote some tests for this. I'm using Mock Service Worker (MSW) to mock the API call. This is my first time for writing these kind of tests so I'm not sure if I'm doing anything wrong, but my understanding was that MSW would intercept the call to the API and return whatever I specify in the MSW config, after that it should follow the regular flow.

Here's my reducer:

const authReducer = (state = INITIAL_STATE, action) => {
    switch (action.type) {

        case actionTypes.REGISTER_NEW_USER:
            const newUser = new User().register(
                action.payload.email,
                action.payload.firstName,
                action.payload.lastName,
                action.payload.password
            )
            console.log("User registered data back:");
            console.log(newUser);
            return {
                ...state,
                'user': newUser
            }
        default:
            return state;
    }
}

this is my User class where the actual call is performed:

import axios from "axios";
import { REGISTER_API_ENDPOINT } from "../../api";

export default class User {

    /**
     * Creates a new user in the system
     *
     * @param {string} email - user's email address
     * @param {string} firstName - user's first name
     * @param {string} lastName - user's last name
     * @param {string} password - user's email address
     */
    register(email, firstName, lastName, password) {
        // console.log("registering...")
        axios.post(REGISTER_API_ENDPOINT, {
            email,
            firstName,
            lastName,
            password
        })
            .then(function (response) {
                return {
                    'email': response.data.email,
                    'token': response.data.token,
                    'active': response.data.active,
                    'loggedIn': response.data.loggedIn,
                }
            })
            .catch(function (error) {
                console.log('error');
                console.log(error);
            });
    }
}

this is my action creator:

export function createNewUser(userData) {
    return {
        type: REGISTER_NEW_USER,
        payload: userData
    }
}

this is the onSubmit method in my Register component:

const onSubmit = data => {
        // console.log(data);
        if (data.password !== data.confirmPassword) {
            console.log("Invalid password")
            setError('password', {
                type: "password",
                message: "Passwords don't match"
            })
            return;
        }

        // if we got up to this point we don't need to submit the password confirmation
        // todo but we might wanna pass it all the way through to the backend TBD
        delete data.confirmPassword

        dispatch(createNewUser(data))
    }

and this is my actual test:

describe('Register page functionality', () => {

    const server = setupServer(
        rest.post(REGISTER_API_ENDPOINT, (req, res, ctx) => {
            console.log("HERE in mock server call")
            // Respond with a mocked user object
            return res(
                ctx.status(200),
                ctx.json({
                'email': faker.internet.email(),
                'token': faker.datatype.uuid(),
                'active': true,
                'loggedIn': true,
            }))
        })
    )

    // Enable API mocking before tests
    beforeEach(() => server.listen());

    // Reset any runtime request handlers we may add during the tests.
    afterEach(() => server.resetHandlers())

    // Disable API mocking after the tests are done.
    afterAll(() => server.close())


    it('should perform an api call for successful registration', async () => {

        // generate random data to be used in the form
        const email = faker.internet.email();
        const firstName = faker.name.firstName();
        const lastName = faker.name.lastName();
        const password = faker.internet.password();

        // Render the form
        const { store } = renderWithRedux(<Register />);

        // Add values to the required input fields
        const emailInput = screen.getByTestId('email-input')
        userEvent.type(emailInput, email);

        const firstNameInput = screen.getByTestId('first-name-input');
        userEvent.type(firstNameInput, firstName);

        const lastNameInput = screen.getByTestId('last-name-input');
        userEvent.type(lastNameInput, lastName);

        const passwordInput = screen.getByTestId('password-input');
        userEvent.type(passwordInput, password);
        const confirmPasswordInput = screen.getByTestId('confirm-password-input');
        userEvent.type(confirmPasswordInput, password);

        // Click on the Submit button
        await act(async () => {
            userEvent.click(screen.getByTestId('register-submit-button'));

            // verify the store was populated
            console.log(await store.getState())
        });
    });

So I was expecting my call to be intercepted whenever the REGISTER_API_ENDPOINT url is detected, and the value of the mocked call to be added to my redux state instead of the value of the actual API call in register method but that doesn't seem to be happening. If that's not the way to test a value in the store, how else can I achieve that?

So at the end of my test, when printing the store I was expecting to see:

{ auth: { user:
{
                'email': faker.internet.email(),
                'token': faker.datatype.uuid(),
                'active': true,
                'loggedIn': true,
            }
}

but instead I'm seeing:

 { auth: { user: null } }

Is this the right approach for this test?

Thanks



from Redux store not being populated when mocking API call

No comments:

Post a Comment