Cypress testing. 5 tips you may find useful.
Tags: cypress, testing, javascript
Published: 2021-07-21 14:50:05

##Table Of Contents:
* [Environment variables](#environment-variables)
* [Change fixtures' values on the fly based on env](#change-fixtures-values-based-on-env)
* [Mock API response globally](#mock-API-response-globally)
* [Custom commands](#actions-to-command)
* [Waiting on a request](#wait-for-requests)


> I suppose in this article that you've already used cypress before so you understand the basics.

&nbsp;
##Environment variables <a name="environment-variables"></a>   

Today mostly while building a web app we all try to use at least two environments. It helps us to ensure that all new changes deployed from previous environments are working as intended before they will be pushed to production. And you probably have different databases, api endpoints and variables for each environment. So,

When it comes to cypress, you can also have a separate config file for each environment.

File structure and file names are optional:

```javascript
/src/cypress/config
                  /test.json
                  /staging.json
                  /production.json
```

Let's take a look at `staging.json`:

```json
{
  "baseUrl": "http://localhost:3000",
  "env": {
    "env": "staging",
    "apiUrl": "https://staging-api.com/api/v1",
  }
}
```

And `production.json`:

```json
{
  "baseUrl": "http://localhost:3000",
  "env": {
    "env": "production",
    "apiUrl": "https://api.com/api/v1",
  }
}
```
*(!)Make sure you store your env variables inside `env` object*

Then update cypress scripts in `package.json` to make sure you run cypress with needed config for each env:

```json
"scripts": {
 "cypress:run:staging": "cypress run --env configFile=staging",
 "test:e2e:staging:run": "start-server-and-test 'npm run start' http://localhost:3000 'npm run cypress:run:staging'",

 "cypress:run:production": "cypress run --env configFile=production",
 "test:e2e:production:run": "start-server-and-test 'npm run start' http://localhost:3000 'npm run cypress:run:production'",
}

// same approach could be used for "cypress open" command.
```
> "start-server-and-test" module runs your app and right after that triggers cypress tests.

Then update `src/cypress/plugins/index.js` with the following code:
```javascript
const fs = require('fs')
const path = require('path')

function getConfigurationByFile(fileName) {
  const pathToConfigFile = path.resolve(__dirname, `../config/${fileName}.json`);
  let rawData;
  try {
    rawData = fs.readFileSync(pathToConfigFile);
  } catch (e) {
    console.error(e);
  }
  return JSON.parse(rawData);
}

module.exports = (on, config) => {
  // `on` is used to hook into various events Cypress emits
  // `config` is the resolved Cypress config

  // this value comes from *cypress run --env configFile=staging*
  const configFile = getConfigurationByFile(config.env.configFile || 'test');

  return { ...config, ...configFile };
};
```

Great! So now when we have our cypress up and running with needed env config, we can use values from that configs in our tests..

If you've done everything correct, then you are able to extract the variables by doing the following:

```javascript
const { apiUrl, env } = Cypress.env();
// to extract baseUrl variable you should use Cypress.config()
// const { baseUrl } = Cypress.config();
```

&nbsp;
##Change fixture's values on the fly based on env <a name="change-fixtures-values-based-on-env"></a>

Basically `fixtures` is being used when you need to mock an API response, **which is not recommended though**
But when you have a few environments sooner or later you will face an issue when the same requests return the same data for each env except a few values(e.g. `id`). And you might not want to duplicate the whole fixture.

In this case all you need to do is to extract a fixture and your env variable; then update needed value on the fly within a test case:

```javascript
describe('it should test smth', function() {
  beforeEach(() => {
    // user is a env variable
    const { user: userEnv } = Cypress.env();

    cy.fixture('user.json').then(user => {
      user.id = userEnv.id; // updating user id
      // use updated fixture here (e.g. `cy.intercept`)
    });
  });
});
```
*(!)If you use `beforeEach`, make sure you wrap it in `describe`, so it won't affect other tests.*
> Here is the link to [fixture docs](https://docs.cypress.io/api/commands/fixture)

&nbsp;
##Mock API response globally <a name="mock-API-response-globally"></a>

To stub network request globally, you should open `src/cypress/support/index.js` file and add the following code:

```javascript
beforeEach(() => {
  cy.intercept({ url: `${apiUrl}/profile*`, middleware: true }, req => {
    req.reply({
      fixture: 'getProfile.json',
    });
});
```
> Here is the link to [itercept docs](https://docs.cypress.io/api/commands/intercept)

&nbsp;
##Custom commands <a name="actions-to-command"></a>

Custom commands in Cypress prevent you from having to add boilerplate code to your tests.
Take a look at this file:

```javascript
 // you can turn this piece of code
 it('should fill in discount form', function() {
    cy.get('input[name="email"]').type('test@test.com');
    cy.get('input[name="phone"]').type('1231231212');
    cy.get('div[role="checkbox"]').click({ force: true });
    cy.findByText(/Save/i).click({ force: true });
    // ...rest of the test
  });

// into a single line
it('should fill in discount form', function() {
 cy.fillDiscountForm();
 // ...rest of the test
});
```
To create `cy.fillDiscountForm()` command you should go over to the file at `src/cypress/support/commands.js` and create a custom command there:
```javascript
Cypress.Commands.add('fillDiscountForm', function() {
  cy.get('input[name="email"]').type('test@test.com');
  cy.get('input[name="phone"]').type('1231231212');
  cy.get('div[role="checkbox"]').click({ force: true });
  cy.findByText(/Save/i).click({ force: true });
});
```
That's it! Now you can use `cy.fillDiscountForm()` in any test.
> Here is the link to [custom commands](https://docs.cypress.io/api/cypress-api/custom-commands)


&nbsp;
##Waiting on a request <a name="wait-for-requests"></a>

Before your app displays any data, it will probably get it from the server. What if you have poor internet connection and all your tests are failing due to unfinished API requests, and lack of the data to display? In this case, and probably every time you make an API call, you should wait (`cy.wait`) for the API call to finish before doing any assertions.

```javascript
it('should fill in discount form', function() {
  cy.intercept(`${apiUrl}/settings`).as('getSettings');
  cy.visit(`/settings-page`);
  // once a request to get settings responds, 'cy.wait' will resolve
  cy.wait('@getSettings');
  // rest of the test
  // cy.contains(/edit settings/i).click({ force: true });
});
```

All we need to do is to register the intercept before visiting the settings page. Once we visit the settings page, it will trigger `GET ${apiUrl}/settings` request, and cypress will wait till it finishes and only after that will continue the testing.

Furthermore, if the API call fails for some reason, then Cypress will display an error and it will be much easier to debug.

> Here is the link to [wait docs](https://docs.cypress.io/api/commands/wait)

&nbsp;

---

*Cover image by <a href="https://unsplash.com/@tma?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Tianyi Ma</a>*