J. Hesters
J. Hesters

Reputation: 14786

Cypress: custom assertion

I'm trying to write a custom assertion in Cypress. I'm testing my API routes using cy.request and found myself duplicating a lot of tests.

context('POST', () => {
  it('returns a 405 with an error message', () => {
    cy.request({
      method: 'POST',
      url: getCurrentUserRoute,
      failOnStatusCode: false,
    }).then(response => {
      expect(response.status).to.equal(405);

      const actual = response.body;
      const expected = {
        message: 'This endpoint only supports the GET method.',
      };

      expect(actual).to.deep.equal(expected);
    });
  });
});

Rinse and repeat for every route and their forbidden methods.

So I wanted to abstract that logic away into a custom Cypress command that acts as a custom assertion.

import { map, toLower, without } from 'ramda';

const asyncForEach = async <T>(
  array: T[],
  callback: (element: T, index: number, array: T[]) => void | Promise<void>,
) => {
  for (let index = 0; index < array.length; index++) {
    await callback(array[index], index, array);
  }
};

const httpMethods = ['get', 'post', 'put', 'delete'];

Cypress.Commands.add(
  'validateMethods',
  (allowedMethods: string[], route: string) => {
    const forbiddenMethods = without(map(toLower, allowedMethods), httpMethods);
    return cy.wrap(
      asyncForEach<string>(forbiddenMethods, async method => {
        context(method, () => {
          it('returns a 405 with an error message', () => {
            return cy
              .request({
                method: 'POST',
                url: route,
                failOnStatusCode: false,
              })
              .then(response => {
                const actual = response.status;
                const expected = 405;

                expect(actual).to.deep.equal(expected);
              });
          });
        });
      }),
    );
  },
);

The problem is, when I run this command, Cypress complains:

The following error originated from your test code, not from Cypress.

  > Cannot call cy.validateMethods() outside a running test.

This usually happens when you accidentally write commands outside an it(...) test.

If that is the case, just move these commands inside an it(...) test.

Check your test file for errors.

When Cypress detects uncaught errors originating from your test code it will automatically fail the current test.

Cypress could not associate this error to any specific test.

We dynamically generated a new test to display this failure.

Check your console for the stack trace or click this message to see where it originated from.

How would I approach this? Should I don't care about abstracting this logic away? Or is there a way to create a helper function that does this for you in Cypress?

Upvotes: 0

Views: 463

Answers (1)

jjhelguero
jjhelguero

Reputation: 2555

You can use cypress-each to iterate over the the routes and their forbidden methods. Each test will run regardless any previous test failures.

const routesAndForbiddenMethods = [
  { route: '/route1', forbiddenMethod: 'POST', supportedMethod: 'GET' },
  { route: '/route1', forbiddenMethod: 'PUT', supportedMethod: 'GET' },
  { route: '/route3', forbiddenMethod: 'DELETE', supportedMethod: 'POST' }
]

it.each(
  routesAndForbiddenMethods,
  (scenario) => `${scenario.forbiddenMethod} to ${scenario.route} returns a 405 with an error message`,
  (scenario) => {
    // your test code
    cy.request({
      method: scenario.forbiddenMethod,
      url: scenario.route,
      failOnStatusCode: false,
    }).then(response => {
      expect(response.status).to.equal(405);

      const actual = response.body;
      const expected = {
        message: `This endpoint only supports the ${supportedMethod} method.`,
      };

      expect(actual).to.deep.equal(expected);
    })
  }
)

Upvotes: 1

Related Questions