Functional Testing with Cypress

Comprehensive guide on Functional Testing with Cypress: setup, advanced configurations, API testing, and best practices.

QA

Nestor Alonso

7/7/20245 min read

Nestor Alonso Functional Testing with Cypress: An Advanced Guide
Nestor Alonso Functional Testing with Cypress: An Advanced Guide

Introduction to Cypress

Cypress is a modern, open-source end-to-end testing tool specifically designed for web applications. Unlike traditional tools such as Selenium, Cypress offers a faster, more reliable, and easier-to-set-up testing experience. This is due to its unique architecture, which allows developers and testers to write and execute automated tests efficiently, facilitating real-time debugging and handling complex assertions.

Advanced Environment Configuration in Cypress

To get started with Cypress, you first need to install it as a development dependency in your project. Run the following command in your terminal:

npm install cypress --save-dev

After installation, you can open Cypress using the following command:

npx cypress open

This command will open the Cypress graphical interface, where you can create and run your tests. Cypress allows the configuration of multiple environments, which is crucial for testing in different settings (development, staging, production). Below is an example of a configuration in the cypress.json file:

{

"baseUrl": "http://localhost:3000",

"env": {

"staging": {

"baseUrl": "http://staging.example.com"

},

"production": {

"baseUrl": "http://example.com"

}

}

}

Creating Robust Test Functions

To avoid repetition and make tests more maintainable, it is useful to create helper functions. For example, to create a login helper function, you can add the following code to cypress/support/commands.js:

Cypress.Commands.add('login', (username, password) => {

cy.visit('/login');

cy.get('#email').type(username);

cy.get('#password').type(password);

cy.get('#login-button').click();

cy.url().should('include', '/dashboard');

});

You can then use this function in your tests as follows:

describe('Login Tests', () => {

it('Should allow a user to log in with valid credentials', () => {

cy.login(Cypress.env('username'), Cypress.env('password'));

});

});

Additionally, you can use hooks like before, beforeEach, after, and afterEach to set up the state before each test runs. This ensures that each test has a clean and prepared environment. Here is an example:

describe('Tests with Setup and Teardown', () => {

before(() => {

cy.log('Preparing test data');

cy.request('POST', `${Cypress.env('apiUrl')}/test/setup`, { setup: true });

});

beforeEach(() => {

cy.login(Cypress.env('username'), Cypress.env('password'));

});

afterEach(() => {

cy.log('Cleaning up after the test');

cy.request('POST', `${Cypress.env('apiUrl')}/test/cleanup`, { cleanup: true });

});

it('Should run a test with setup and teardown', () => {

cy.visit('/');

cy.get('#dashboard').should('be.visible');

});

});

API Testing with Cypress

Cypress includes robust functionalities for API testing, allowing you to perform HTTP requests and validate responses directly in your tests. Here is an example of an API authentication test:

describe('API Tests - Authentication', () => {

it('Should authenticate the user and get a token', () => {

cy.request('POST', `${Cypress.env('apiUrl')}/auth/login`, {

username: Cypress.env('username'),

password: Cypress.env('password')

}).then((response) => {

expect(response.status).to.eq(200);

cy.wrap(response.body).its('token').as('authToken');

});

});

});

For CRUD tests, the following example shows how to create, read, update, and delete a resource:

describe('API Tests - CRUD', () => {

let authToken;

before(() => {

cy.request('POST', `${Cypress.env('apiUrl')}/auth/login`, {

username: Cypress.env('username'),

password: Cypress.env('password')

}).then((response) => {

authToken = response.body.token;

});

});

it('Should create a new resource', () => {

cy.request({

method: 'POST',

url: `${Cypress.env('apiUrl')}/resource`,

headers: {

Authorization: `Bearer ${authToken}`

},

body: {

name: 'New Resource',

value: 'Value'

}

}).then((response) => {

expect(response.status).to.eq(201);

cy.wrap(response.body).its('id').as('resourceId');

});

});

it('Should read the created resource', function () {

cy.request({

method: 'GET',

url: `${Cypress.env('apiUrl')}/resource/${this.resourceId}`,

headers: {

Authorization: `Bearer ${authToken}`

}

}).then((response) => {

expect(response.status).to.eq(200);

expect(response.body.name).to.eq('New Resource');

});

});

it('Should update the created resource', function () {

cy.request({

method: 'PUT',

url: `${Cypress.env('apiUrl')}/resource/${this.resourceId}`,

headers: {

Authorization: `Bearer ${authToken}`

},

body: {

name: 'Updated Resource',

value: 'New Value'

}

}).then((response) => {

expect(response.status).to.eq(200);

expect(response.body.name).to.eq('Updated Resource');

});

});

it('Should delete the created resource', function () {

cy.request({

method: 'DELETE',

url: `${Cypress.env('apiUrl')}/resource/${this.resourceId}`,

headers: {

Authorization: `Bearer ${authToken}`

}

}).then((response) => {

expect(response.status).to.eq(204);

});

});

});

Advanced Examples of Functional Tests with Cypress

In this section, I will provide detailed test cases and practical examples of how to implement various functional tests using Cypress. These examples expand on each type of test mentioned above with multiple scenarios.

Functional Test of a Contact Form

To set up a functional test of a contact form, add the following command to cypress/support/commands.js:

Cypress.Commands.add('fillContactForm', (name, email, message) => {

cy.get('#name').type(name);

cy.get('#email').type(email);

cy.get('#message').type(message);

cy.get('#send-button').click();

});

Then, use this command in your tests:

describe('Contact Form', () => {

beforeEach(() => {

cy.visit('/contact');

});

it('Should allow the user to send a contact message', () => {

cy.fillContactForm('John Doe', 'john@doe.com', 'This is a test message.');

cy.contains('Message sent successfully').should('be.visible');

});

});

Functional Test of Registration and Login

Set up commands for registration and login in cypress/support/commands.js:

Cypress.Commands.add('registerUser', (username, email, password) => {

cy.visit('/register');

cy.get('#username').type(username);

cy.get('#email').type(email);

cy.get('#password').type(password);

cy.get('#register-button').click();

});

Cypress.Commands.add('loginUser', (email, password) => {

cy.visit('/login');

cy.get('#email').type(email);

cy.get('#password').type(password);

cy.get('#login-button').click();

});

Use these commands in your tests:

describe('Registration and Login', () => {

it('Should allow the user to register and then log in', () => {

cy.registerUser('newUser', 'new@user.com', 'password123');

cy.contains('Registration successful').should('be.visible');

cy.loginUser('new@user.com', 'password123');

cy.url().should('include', '/dashboard');

});

});

Functional Test of a Complete Purchase

Add commands for adding to cart and proceeding to checkout in cypress/support/commands.js:

Cypress.Commands.add('addToCart', (productName) => {

cy.get('.product-list').contains(productName).click();

cy.get('#add-to-cart').click();

});

Cypress.Commands.add('checkout', (address, paymentMethod) => {

cy.get('#checkout').click();

cy.get('#shipping-address').type(address);

cy.get('#payment-method').select(paymentMethod);

cy.get('#place-order').click();

});

Perform the complete purchase test using these commands:

describe('Complete Purchase', () => {

before(() => {

cy.loginUser(Cypress.env('username'), Cypress.env('password'));

});

it('Should allow the user to search for a product, add it to the cart, and complete the purchase', () => {

cy.visit('/');

cy.addToCart('product1');

cy.checkout('123 Main Street', 'Credit Card');

cy.get('#order-confirmation').should('be.visible').and('contain', 'Thank you for your purchase');

});

});

Differences between Functional and Non-Functional Tests

Functional Tests

Functional tests aim to verify the actions and functionalities of the system according to user or business requirements. These tests can be performed manually or automated. Examples of functional tests include unit tests, integration tests, system tests, and user acceptance tests.

Non-Functional Tests

On the other hand, non-functional tests focus on aspects such as performance, security, and usability of the system. These tests are generally automated and aim to ensure that the system meets user expectations and experiences. Examples of non-functional tests include performance tests, security tests, recovery tests, and usability tests.

Functional Testing Tools

Cypress stands out as a modern and efficient tool for automated functional testing. It offers a simple setup, an intuitive API, and robust real-time debugging capabilities, facilitating the creation and execution of tests for web applications.

Key Features of Cypress

  • Real-Time: Watch tests run in real-time.

  • Auto-Reloads: Tests automatically reload when you make changes.

  • Total Control: Control and simulate every part of your application.

  • Asynchronous: Efficiently handle asynchronous testing.

Conclusion

Functional testing is essential to ensure that applications meet requirements and function correctly. Cypress provides a powerful and user-friendly platform for automating these tests, improving software quality and testing process efficiency. Implementing Cypress in the functional testing workflow can help businesses deliver high-quality products more quickly and reliably.