Cypress: Mastering Function Call Observation with Spies


What it does

  • Tracks information about how the function is called, such as:
    • Number of times it's been called (call count)
    • Arguments passed to the function during each call
    • Return values from the function (if applicable)
  • Wraps a function in your application, turning it into a spy object.

Benefits of using spies

  • Mocks return values of the function for testing purposes (although Cypress uses stubs for that specifically).
  • Asserts the arguments passed to the function during each call.
  • Checks how many times a function is called.
  • Verifies if a function is called during your test.

Using cy.spy()

The syntax for using cy.spy() is:

cy.spy(object, 'methodName')
  • Replace 'methodName' with the actual name of the function you want to track.
  • Replace object with the object containing the function you want to spy on.

Example

Imagine you have a function called greetUser that takes a name as an argument. You can use cy.spy() to track its behavior in your test:

const userService = {
  greetUser(name) {
    return `Hello, ${name}!`;
  }
};

cy.spy(userService, 'greetUser');

// Simulate calling the function with a name
userService.greetUser('John');

// Now you can use assertions on the spy object
cy.get('@greetUser').should('be.calledOnce'); // Assert it's called once
cy.get('@greetUser').should('have.been.calledWith', 'John'); // Assert the argument passed


Checking Call Count

This example verifies a function is called twice during the test:

const api = {
  fetchData() {
    // Simulate fetching data
  }
};

cy.spy(api, 'fetchData');

// Trigger the function calls you want to test
api.fetchData();
api.fetchData();

// Assert the spy was called twice
cy.get('@fetchData').should('be.calledTwice');

Matching Arguments

This example checks if a function is called with specific arguments:

const form = {
  submitForm(name, email) {
    // Submit form logic
  }
};

cy.spy(form, 'submitForm');

// Simulate form submission
form.submitForm('Alice', '[email protected]');

// Assert the spy was called with specific arguments
cy.get('@submitForm').should('have.been.calledWith', 'Alice', '[email protected]');

Aliasing Spies for Readability

This example uses an alias to improve test code readability:

const notificationService = {
  showSuccess(message) {
    // Show success notification
  }
};

const showSuccessSpy = cy.spy(notificationService, 'showSuccess').as('showSuccess');

// Trigger the function
notificationService.showSuccess('Form submitted!');

// Assert using the alias
cy.get('@showSuccess').should('be.calledOnce');
cy.get('@showSuccess').should('have.been.calledWith', 'Form submitted!');


Using Assertions on DOM Elements

If your function call ultimately leads to changes in the DOM (Document Object Model) of your application, you can assert on those changes instead of directly spying on the function.

For example, if the greetUser function updates a heading element with the user's name, you could use Cypress commands like cy.get() and cy.contains() to verify the updated content:

userService.greetUser('John');

cy.get('h1').should('contain', 'Hello, John!');

Mocks and Stubs with Libraries

For more complex scenarios where you need to control the behavior of a function during testing, consider using external libraries like Sinon.js alongside Cypress.

  • Sinon.js provides a powerful mocking and stubbing framework. You can create mocks that replace actual functions and define their behavior for your tests.
  • Stubs replace a function and define its behavior during the test, allowing you to control its return values or thrown errors.
  • Spies observe how a function is called but don't alter its behavior.

Using Sinon with Cypress requires additional setup, but it offers more flexibility for manipulating functions during testing.

Mocking Frameworks for Specific Testing Tools

If you're using a specific testing framework with Cypress (like Mocha or Jest), explore their built-in mocking functionalities. These frameworks might have their own ways to mock functions for testing purposes.