Search code examples
reactjsjestjsamazon-cognitocreate-react-app

Trying to mock cognitoUser.authenticateUser from amazon-cognito-identity-js and getting that it's undefined


I am trying to write some unit tests in Jest for a Create React App project I have which uses Amazon Cognito.

The code under test:

const getNewCognitoUser = (username) => {
    const userData = {
        Username: username,
        Pool: userPool,
    };
    return new CognitoUser(userData);
};

const setAWSCredentials = (jwt) => {
    return new Promise((resolve, reject) => {
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
            IdentityPoolId: appConfig.IdentityPoolId,
            Logins: {
                [appConfig.Logins.cognito.identityProviderName]: jwt,
            },
        });
        AWS.config.credentials.clearCachedId();
        AWS.config.credentials.refresh((err) => {
            err ? reject(err) : resolve();
        });
    });
};

export const authenticate = (username, password) => {
    const authenticationDetails = new AuthenticationDetails({
        Username: username,
        Password: password,
    });

    cognitoUser = getNewCognitoUser(username);

    return new Promise((resolve, reject) => {
        cognitoUser.authenticateUser(authenticationDetails, {
            onSuccess: function (result) {
                setAWSCredentials(result.idToken.jwtToken)
                    .then(() => {
                        resolve(result);
                    })
                    .catch((err) => {
                        reject(err);
                    });
            },
            onFailure: function (err) {
                reject(err);
            },
            newPasswordRequired: function (userAttributes, requiredAttributes) {
                reject({
                    code: "PasswordResetRequiredException",
                    message: "New Password Required",
                    newPasswordRequired: true,
                });
            },
        });
    });
};

The test

import { authenticate } from "../user";
import { CognitoUser } from "amazon-cognito-identity-js";
import AWS from "aws-sdk";

jest.mock("amazon-cognito-identity-js", () => {
    const mCognitoUser = {
        authenticateUser: jest.fn((authenticationDetails, callbacks) => {
            if (callbacks && callbacks.onSuccess) {
                // Simulate a successful authentication

                callbacks.onSuccess({ idToken: { jwtToken: "fakeToken" } });
            }
        }),
    };

    return {
        CognitoUser: jest.fn(() => mCognitoUser),

        CognitoUserPool: jest.fn(),

        AuthenticationDetails: jest.fn(),

        CognitoUserAttribute: jest.fn(),
    };
});

jest.mock("aws-sdk", () => {
    const mCognitoIdentityCredentials = jest.fn().mockImplementation(() => {
        return {
            clearCachedId: jest.fn(),

            refresh: jest.fn(),
        };
    });

    return {
        CognitoIdentityCredentials: mCognitoIdentityCredentials,
        config: {},
    };
});

describe("do I understand mocks", () => {
    test("let's find out", async () => {
        console.log(CognitoUser);
        const mCognitoUser = new CognitoUser();
        console.log(mCognitoUser);
        console.log(mCognitoUser.authenticateUser);

        await authenticate("foo", "bar");
        expect(CognitoUser.authenticate).toBeCalledTimes(1);
    });
});

This tells me that the function authenticateUser is undefined and of course it fails when trying to run

await authenticate("foo", "bar")

stating that

TypeError: cognitoUser.authenticateUser is not a function

AFAICT, the code is properly mocked, so why doesn't it see that there is a mock implementation for authenticateUser?


Solution

  • I found the solution.

    The app was generated using Create React App and the default setting for resetMocks is true, which has the effect of blowing away the mock implementations I set up. I am not sure why that's the default, but adding the following to my package.json file solved the problem.

        "jest": {
            "resetMocks": false
        }