Use Cypress to test AWS Amplify Apps with Authentication
TL;DR - E2E test your AWS Amplify application without worrying about your authentication!
- You protected your app with authentication using the
withAuthentication()
HOC or<AmplifyAuthenticator>
. - You are using Cypress to build E2E tests
- You're on the right track!
Challenge: Before every test scenario you must now sign in! How am I going to deal with that?!
You don't want to use your UI to login before every test scenario!
If you would use your UI to login before every test scenario you'd have to clutter every test scenario with code to automatically fill in your credentials.
We want a simple cypress command
cy.signIn()
that does the work for us!
This blog shows you how to do that and has a link to a github repo containing the example used throughout the blog.
Prerequisits
- Create a test user in your cognito user pool and remember the user and password.
-
Your
package.json
contains:"devDependencies": { "cypress": "^5.3.0", "cypress-localstorage-commands": "^1.2.2", "aws-amplify": "^3.3.4" }
-
Your
cypress.json
contains:{ "baseUrl": "http://localhost:3000", "includeShadowDom": true }
Configuration
In the root of your project create a file cypress.env.js
. Add the file to your .gitignore
'cause you don't want to commit your credentials.
In this file configure your username
, password
, userPoolId
and clientId
.
The first two you created when creating your user in the userpool.
The last two you can find in the aws-exports.js
file.
cypress.env.js
:
{
"username": "yourUserame",
"password": "yourPassword",
"userPoolId": "eu-west-1_BYZ5DzkpO",
"clientId": "79audohaaahb432ovoa240asaj"
}
Custom commands can by created in cypress/support/commands.js
In commands.js
add the following configuration code:
const Auth = require("aws-amplify").Auth;
import "cypress-localstorage-commands";
const username = Cypress.env("username");
const password = Cypress.env("password");
const userPoolId = Cypress.env("userPoolId");
const clientId = Cypress.env("clientId");
const awsconfig = {
aws_user_pools_id: userPoolId,
aws_user_pools_web_client_id: clientId,
};
Auth.configure(awsconfig);
Create custom Cypress signIn command
Cypress allows us to create custom Cypress commands.
That means, awesome... we can create our custom signIn
command!
As we said before, you can configure custom commands in cypress/support/commands.js
Amplify, what else?
We will use the Amplify javascript library to aid us with the authentication.
Look at that:
commands.js
:
Cypress.Commands.add("signIn", () => {
cy.then(() => Auth.signIn(username, password)).then((cognitoUser) => {
const idToken = cognitoUser.signInUserSession.idToken.jwtToken;
const accessToken = cognitoUser.signInUserSession.accessToken.jwtToken;
const makeKey = (name) =>
`CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.${cognitoUser.username}.${name}`;
cy.setLocalStorage(makeKey("accessToken"), accessToken);
cy.setLocalStorage(makeKey("idToken"), idToken);
cy.setLocalStorage(
`CognitoIdentityServiceProvider.${cognitoUser.pool.clientId}.LastAuthUser`,
cognitoUser.username
);
});
cy.saveLocalStorage();
});
How does this work?
- We use the amplify
Auth
class to create a session for our user. This command returs aCognitoUser
. - The
Auth.singIn()
command needs to be wrapped incy.then()
because of the way cypress handles promises. - Extract the idToken and accessToken from the cognitoUser.
- The cognito credentials need to have a specific format, hence the
makeKey
function. - Use
cy.setLocalStorage
to save the credentials to localStorage. ThesetLocalStorage
function originates from thecypress-localstorage-commands
dependency that we installed via our dev dependencies. - We save our localStorage using
cy.saveLocalStorage
.
Sign In before your test!
Before running a test spec we will have to signIn and preserve our localStorage during the run of these tests.
After running all tests in the spec we want to clear localStorage since we don't want to build up any state between tests.
Here is an example of how to do that using the before()
, beforeEach
, after()
and afterEach()
functions.
describe("Example test", () => {
before(() => {
cy.signIn();
});
after(() => {
cy.clearLocalStorageSnapshot();
cy.clearLocalStorage();
});
beforeEach(() => {
cy.restoreLocalStorage();
});
afterEach(() => {
cy.saveLocalStorage();
});
it("should be logged in", () => {
cy.visit("/");
cy.get(".App-logo").should("be.visible");
it("Should talk about react", () => {
cy.visit("/")
cy.contains("React")
})
});
});
End result!
Start your app: npm start
.
In another terminal open you cypress tests: npm run cypress:open
.
When we run our test suite we no longer have to worry about authenticating in the UI!
Resources
Want to try it out yourself!?
Checkout this repository to play with the example that was featured here: https://github.com/Nxtra/cypress-amplify-auth-test.
Credits
Featured image by iMattSmart on Unsplash.