Blog

React application testing basics via Cypress

August 7, 2024

Today, let’s look at how to automate React application testing with the Cypress tool.

To start working with Cypress in a React project, the first thing you need to do is install the package itself. This can be done using npm or Yarn:

npm install cypress --save-dev
# or
yarn add cypress --dev

After installation, you will need to initialize Cypress, which will create a basic structure of folders and configuration files:

npx cypress open

The command will create all the necessary files and open the Cypress user interface where you can see and manage all the tests.

The structure will look as follows:

  • cypress/fixtures/: folder for storing fixtures – files with data that can be used in tests to simulate server responses, data loading, etc.
  • cypress/integration/: this is where the tests are located. Cypress automatically searches for tests in this directory.
  • cypress/plugins/: folder for configuring plugins that can change Cypress behavior at a lower level, we’ll talk about them later.
  • cypress/support/: contains files that are executed before the tests are loaded. This is an ideal place to place reusable functions or settings that are available globally in all tests.
  • cypress.json: Cypress configuration file, where you can specify various settings such as base URLs, wait times for tests, environment variables, etc.
  • package.json: is a standard Node.js file containing information about the project and its dependencies. In this file you can also configure scripts to run tests.

Example of a basic cypress.json configuration:

{
"baseUrl": "http://example.com",
"defaultCommandTimeout": 10000,
"pageLoadTimeout": 30000,
"viewportWidth": 1280,
"viewportHeight": 720,
"testFiles": "**/*.spec.js"
}

Here:

  • baseUrl specifies the base URL to which relative URLs will be appended in your tests.
  • defaultCommandTimeout specifies the default timeout for Cypress commands in milliseconds (10 seconds in this case).
  • pageLoadTimeout sets the maximum time to wait for a page to load (30 seconds).
  • viewportWidth and viewportHeight specify the width and height of the browser window for running tests.
  • estFiles specifies a template for test files (in this case, all files with the .spec.js extension in any subfolders).

Basic syntax and structure of tests in Cypress

Cypress uses a chain of commands. Tests are usually organized using the describe and it functions, which are part of the global Mocha API.

Example of a basic test:

describe('Login Test', function() {
	it('Visits the login page', function() {
	  cy.visit('https://example.com/login') // login page
	  cy.get('input[name=username]').type('user1') // username
	  cy.get('input[name=password]').type('password123') // password
	  cy.get('form').submit() // form submission
	  cy.url().should('include', '/dashboard') // URL check after login
	})
  })

Items search is done with cy.get(), which accepts a selector string. Cypress supports most CSS selectors.

Interaction with elements is possible through commands like .click(), .type(), .check() for different types of elements.

Assertions check the expected state or behavior of elements. Cypress integrates with Chai by providing multiple assertions via .should() or .expect().

Examples:

cy.get('.list-item').should('have.length', 5) // element count check
cy.get('.alert').should('not.exist') // the element must not exist

Cypress automatically manages asynchrony, so you will very rarely have to explicitly use async/await. For example, if you make a request to an API, Cypress will wait for it to complete before moving on:

cy.request('POST', '/api/users', {name: 'Alex'}).then((response) => {
	expect(response.body).to.have.property('name', 'Alex') // response check
})

Cypress allows HTTP requests to be intercepted, making it possible to test without actual server responses using mocks:

cy.intercept('GET', '/api/users', {fixture: 'users.json'}).as('getUsers')
cy.wait('@getUsers').its('response.statusCode').should('eq', 200)

You can use aliases to save data and use it later in tests:

cy.get('input[name=firstname]').type('Jane').as('firstname')
cy.get('@firstname').should('have.value', 'Jane')

Cypress can take snapshots of the application’s state:

cy.visit('/settings')
cy.get('.theme').click()
cy.screenshot() // screenshot

Examples of use

Counter testing

For a counter component that increments or decrements a value when buttons are pressed, tests in Cypress can check the initial state and response to user actions:

describe('Counter Component', () => {
	beforeEach(() => {
	  cy.visit('/');
	});
  
	it('should render the counter with initial value', () => {
	  cy.get('h1').contains('Counter: 0');
	});
  
	it('should increment the counter when increment button is clicked', () => {
	  cy.get('button').contains('Increment').click();
	  cy.get('h1').contains('Counter: 1');
	});
  
	it('should decrement the counter when decrement button is clicked', () => {
	  cy.get('button').contains('Increment').click();
	  cy.get('button').contains('Decrement').click();
	  cy.get('h1').contains('Counter: 0');
	});
  });

The test verifies that the initial display of the component is correct and changes as the user interacts with the controls.

Input form testing

Input form testing includes testing user input and the form’s response to submission:

describe('Login Form', () => {
	beforeEach(() => {
	  cy.visit('/login');
	});
  
	it('should allow a user to type in the username and password', () => {
	  cy.get('input[name="username"]').type('testuser');
	  cy.get('input[name="password"]').type('qwerty');
	});
  
	it('should show an error message for invalid credentials', () => {
	  cy.get('input[name="username"]').type('invaliduser');
	  cy.get('input[name="password"]').type('wrongpassword');
	  cy.get('button[type="submit"]').click();
	  cy.get('.error-message').should('be.visible').and('contain', 'Invalid credentials');
	});
  
	it('should redirect to dashboard on successful login', () => {
	  cy.get('input[name="username"]').type('testuser');
	  cy.get('input[name="password"]').type('qwerty');
	  cy.get('button[type="submit"]').click();
	  cy.url().should('include', '/dashboard');
	});
  });

The test suite checks both negative and positive login scenarios.

Task list testing

Test the addition, deletion and validation of list items:

describe('Todo List Functionality', () => {
	beforeEach(() => {
	  cy.visit('/todos');
	});
  
	it('displays the list of todos correctly', () => {
	  cy.get('li').should('have.length', 2); // предположим, что уже есть два элемента в списке
	});
  
	it('adds a new todo', () => {
	  const newItem = 'Complete the test';
	  cy.get('.new-todo-input').type(`${newItem}{enter}`);
	  cy.get('li').should('have.length', 3);
	});
  
	it('deletes a todo', () => {
	  cy.get('li').first().find('.delete-button').click();
	  cy.get('li').should('have.length', 1);
	});
  });

Various plugins

Cypress not only allows you to test your application but also extends its capabilities through plugins, let’s take a look at my favorites.

cypress-axe

The cypress-axe plugin integrates axe-core accessibility tools with Cypress, allowing you to test web pages against accessibility standards:

it('Tests accessibility on the page', () => {
	cy.visit('/your-page');
	cy.injectAxe();
	cy.checkA11y();
  });

cypress-plugin-tab

The plugin adds support for keyboard events, such as pressing the Tab key, which is not supported in the base version of Cypress:

it('should be able to navigate fields with tab', () => {
	cy.get('#first-input').type('info').tab().type('more info');
  });

cypress-graphql-mock

The cypress-graphql-mock plugin allows you to mock GraphQL responses, which is good for developing and testing client applications that use GraphQL to interact with the server:

it('mocks GraphQL data', () => {
	cy.mockGraphQL({ Query: { user: () => ({ id: 1, name: 'John' }) } });
	cy.visit('/user-profile');
	cy.contains('John');
  });

The test verifies that the UI correctly displays user data by mocking GraphQL queries.

cypress-downloadfile

The cypress-downloadfile plugin adds the ability to download files while running tests:

it('downloads a file', () => {
	cy.downloadFile('http://example.com/file.pdf', 'mydownloads', 'example.pdf');
	cy.readFile('mydownloads/example.pdf').should('exist');
  });

This downloads the PDF file from the provided URL, saves it to a directory and further checks if the file is available on the system.

cypress-localstorage-commands

The cypress-localstorage-commands plugin adds commands to manage data in localStorage during testing:

beforeEach(() => {
	cy.restoreLocalStorage();
  });
  
  afterEach(() => {
	cy.saveLocalStorage();
  });
  
  it('tests local storage', () => {
	cy.setLocalStorage('testKey', 'testValue');
	cy.getLocalStorage('testKey').should('eq', 'testValue');
  });

Here, the state of localStorage is restored before each test and saved after the test.

Ad+

Get in touch

sergeiuzabila@gmail.com