Loading and using a fixture from an API endpoint for Cypress e2e tests
Tags: cypress, vue, javascript
Published: 2020-02-19 10:01:05

I love Cypress, it’s one of those tools that for me has just made e2e testing actually fun and engaging. In this article, we’re going to explore a common scenario that you may encounter when doing e2e testing on your apps: fetching a data `fixture` from an API endpoint before your tests and using it to write your tests.


## What is a Cypress fixture?

In case you haven’t heard of fixtures before, you can think of them as a predefined piece of data that you use in your tests to perform certain actions.

Consider the following fixture example.

```js
    {
      "username": "yoda",
      "password": "secureiam",
      "id": 123
    }
```

If we have an endpoint in our application that for example holds the user’s settings page, we may need to construct the following URL:

```js
    http://www.holocrons.com/123/settings
```

In this demo URL, the first segment is the `id` of the user, so based on our dummy fixture it would be `123`.

In Cypress, to navigate to a URL you use the `visit` command, so you may want to construct your action as follows:

```js
    cy.visit('/123/settings')
```

The problem here however is that we are hardcoding a user id into our tests. In most cases this will become a problem, because unless our backend and database are set up to work with a specific id, if that user ceases to exist for some reason then all of our e2e tests will break.

The first solution is to create a local fixture, so you would go into your cypress folder structure, and create a `user.json` file inside the appointed `fixtures` folder. Inside, we would paste the content of our demo fixture.

```js
    # user.json
    {
      "username": "yoda",
      "password": "secureiam",
      "id": 123
    }
```

Now, in order to use this fixture inside our test, we could set up a `beforeEach` hook inside our spec file, and load it as follows.

```js
    describe('my tests', () => {
      beforeEach(function() {
        cy.fixture('path/to/user.json').as('userData').then(() => {
          // Do some things like setting up routes. 
        });
      })
    })
```

The Cypress `fixture` method takes a string param that points to where the `fixture` file is located. This path is based off where your root `cypress.json` file is located.

Once the file is loaded into the fixture, you can use `then` block to trigger some extra actions like setting up your `cy.server()` and some routes.
Notice that in this example we’re setting up an alias with `.as(``'``userData``'``)`. This is important if you’re going to be using the loaded fixture data later on in your tests, outside of the `then` block.

Let’s now take our new fixture `userData` and actually use it to set up some routes. Because we are inside the `then` block you have two choices, you can receive as the param to the method the newly loaded `fixture`, or you can use `this` to access it as a property.

In these examples we’re going to use the `this` approach, but be warned - in order for `this` to keep the correct context, we **have** to use regular `function ()` syntax, not arrow functions on our `it` calls! If you don’t do this, you will get `undefined` errors due to how arrow functions `this` context works.

Let’s create a `router` alias for our settings page.

```js
    describe('my tests', () => {
      beforeEach(function() {
        cy.fixture('path/to/user.json').as('userData').then(() => {
          // Do some things like setting up routes. 
          cy.server()
          cy.route(`${this.userData.id}/settings`).as('userSettings')
        });
      })
    })
```

Notice how in this case we are using `this.userData.id` to explicitly declare that our `route` needs to point to user id `123`, the one that was declared in the fixture. Now we can access any data inside our user.json file through the `this.userData` property.

Here’s another example inside an `it` block.

```js
    it('shows the users username in the header', function() {
      cy.visit(`${this.userData.id}/settings`)
      cy.contains('h1', this.userData.username)
    })
```

Our tests are now hard-code free. But what happens when our tests are driven by some sort of function or endpoint that populates our database with dummy data? We are now going to look at the same problem/solution, but by loading the `fixture` from an API endpoint.


## Loading API based fixtures

In order to get our fixture from an API, we are going to create some Cypress custom commands to make the code easier to use in our tests.

Head over to your `commands.js` file, and let’s get started.

```js
    Cypress.Commands.add('loadFixture', (savePath) => {
      cy.request({
        method: 'post',
        url: `api/path/to/fixture/endpoint`,
        timeout: 50000
      }).then(res => {
        cy.log('Fixture loaded from API');
    
        cy.writeFile(savePath, res.body);
        cy.log('Fixture written to disk');
      });
    });
```

Let’s take a closer look at the code. First we create a new Cypress command and call it `loadFixuture`, it will receive a single param called `savePath`. This will be a string, and the path to where our fixture will be saved in the disk.

Inside our command, we first call `cy.request` to make a network request to our fixture endpoint in the API. This endpoint should give us a JSON response, so make sure to adjust the `url` paramter to match your app’s needs - you can also of course pass paramters in the body or query string as needed. Check out the documentation for `cy.request` for more options.

After the request completes, we chain a `then` callback that gives us the result from our request - this `res` holds the response from the API. 

We make a couple of `cy.log` statements so that we can track from our Cypress log what is happening, and finally call a `cy.writeFile` command with the `savePath` that we passed to our command, and the `body` from the network response. This will write the JSON to a file.

You may be wondering at this point why we are writing the network result to a file, we need this information to be in a file so that we can later on read it using the `cy.fixture` command.

With our new command, we can now update our tests to use the new loading.

```js
    describe('my tests', () => {
      before(function() {
        cy.loadFixture('path/to/user.json')
      })
    
      beforeEach(function() {
        cy.fixture('path/to/user.json').as('userData').then(() => {
          // Do some things like setting up routes. 
        });
      })
    })
```

Notice that we are now downloading the fixture on the `before` hook, that way we can guarantee that it is only downloaded once since this type of request can usually be heavy on the server and database.

Now, whenever we call the `beforeEach` as we did before we will actually be targeting a file that has been downloaded and written before our tests begin, this also guarantees that if necessary you will be working with new generated data.

As always, thanks for reading and share with me your comments on twitter at: [@marinamosti](https://twitter.com/marinamosti)
PS. All hail the magical avocado 🥑
PSS. ❤️🔥🐶☠️