Cypress

Cypress is an integration/feature testing framework for node based web servers. When Cypress tests are executed they create a headerless browser through which Cypress contacts a test server version of the web server being tested and runs integration tests created by the developer.

You can run a single specific integration test file by using the run --spec command followed by a path that points to the file which contains the Cypress integration test.

$ npx cypress run --spec path/to/file.spec.js

Tasks

Because Cypress is an integration framework designed for testing the front end of your application it executes in a headless browser that does not have access to your server code. This means that any configuration code that you might want to use for tests, like database insertions, cannot be executed directly inside a Cypress test.

If you want to execute server side configuration requests during your Cypress tests you should use the cy.task method. This method takes the name of a task defined in the plugins/index.js of your Cypress directory and runs it. In the index.js below we use the on callback to define a method that will executed when task is called with the name of the function in task passed to it. Cypress tasks must explicitly return a value, nothing or a promise to indicate to the main test calling them that the task has finished executing, this is why the tasks below return null after they have printed to the console.

// cypress/plugins/index.js
module.exports = function(on) {
  on('task', {
    myTask() {
      console.log('This task was executed');
      return null;
    }
  });
}

This is the corresponding Cypress test that calls the myTask function by name when it executes using the cy.task method.

// call_task_spec.js
describe('Task Example', function() {
  it('Calls a task', function() {
    cy.task('myTask');
  });
});
// => This task was executed.

You can define multiple possible tasks that could be called by name in the plugins/index.js file by comma separating them within the second argument object to the on method.

module.exports = function(on) {
  on('task', {
    myTask() {
      console.log('This task was executed');
      return null;
    },
    anotherTask() {
      console.log('This is another task');
      return null;
    }
  });
}

You can pass argument values from your cypress tests to tasks by adding a second argument after the task name specification and an input argument to the task definition in plugins/index.js.

// cypress/plugins/index.js
module.exports = function(on) {
  on('task', {
    myTaskWithArgument(arg) {
      console.log(arg);
      return null;
    }
  });
}

The corresponding test can then pass in an argument value. This input argument can take many different argument forms, such as a callback, object or other class parameter to be used by your Cypress tasks.

// call_task_with_argument_spec.js
describe('Task Example', function() {
  it('Calls a task', function() {
    cy.task('myTask', 'an argument message');
  });
});
// => 'an argument message'

Asynchronous cypress tasks should return a Promise that resolves when the asynchronous behaviour has completed. This can then be combined with the then keyword inside your cypress test to execute the rest of the test once the asynchronous task has completed.

// cypress/plugins/index.js
module.exports = function(on) {
  on('task', {
    myAsyncTask() {
      return new Promise(function(resolve) {
        mongoose.connect('mongodb://localhost/mydatabase_test', function(err) {
          // do some database stuff
          resolve('done');
        });
      });
    }
  });
}

In the corresponding cypress test file where the task is called we use then to execute once a database connection is achieved.

// call_task_with_async_spec.js
describe('Task Example', function() {
  it('Calls a task', function() {
    cy.task('myAsyncTask').then(function(result) {
      // do some more tests here now the async task has finished.
    });
  });
});

It’s important to note that promises returned by cy.task MUST have a return value on their resolve. You cannot return an empty promise back to the cypress test calling the task, otherwise it will give you a misleading error saying that a promise is not even being returned.

You can combine the use of task promises with async and await behaviour within your cypress test to streamline your test calls to async functions. In the example below, the it function definition is changed to async and cy.task instead of using then uses await and then executes the rest of the test code once it is finished.

// call_task_with_async_await_spec.js
describe('Task Example', function() {
  it('Calls a task', async function() {
    await cy.task('myAsyncTask');
    // rest of cypress test
  });
});

You can set the timeout limit of task manually when calling it by submitting as the third argument to the cy.task method an object with a property timeout linked with the maximum wait time for a task to execute in milliseconds. The below code passes in null for the second argument, because there is not explicit argument for this task and sets the timeout to 20 seconds.

// call_task_with_custom_timeout_spec.js
describe('Task Example', function() {
  it('Calls a task', function() {
    cy.task('myTask', null, {timeout: 20000});
  });
});
// => This task was executed.

Mongoose database and Tasks

Cypress tasks do not maintain a direct connection to the database even if your server code uses the database Cypress does not have access to that connection, this means that if you want to execute database seeding or dropping during a Cypress test with cy.task you will need to establish a new database connection. The below example demonstrates importing mongoose and a User model into the plugins/index.js file and then connecting to a database before creating a new mongoose model instance and saving the new object to the database and then calling resolve on the promise to signal that the async behaviour of the function is complete.

// cypress/plugins/index.js
var mongoose = require('mongoose');
var User = require('../../models/user');

module.exports = function(on) {
  on('task', {
    myAsyncTask() {
      return new Promise(function(resolve) {
        mongoose.connect('mongodb://localhost/mydatabase_test', function(err) {
          var newUser = new User({name: 'dec'});
          newUser.save(function(err) {
            resolve('done');
          });
        });
      });
    }
  });
}