Explain It Like I'm 5: Cypress Fixtures
Doing research on Cypress Fixtures makes me feel like this:
Google “Cypress Fixtures” and there are so many tutorials and videos on the topic. It’s hard to know where to begin.
Having zero knowledge of Fixtures, what’s helpful for me is dialing down to the very basics of fixtures, what they are for, how they are created, and how to use them in tests.
Here’s my first installment of Explain It Like I’m 5: Cypress Fixtures edition.
Assumptions I’m making:
you already have some working knowledge of Cypress
you know little to zero when it comes to fixtures (no worries, me too 🙋🏻)
you have a decent understanding between Typescript and Javascript. I’m working with Typescript so if your project does not use TS, expect the syntax to look slightly different than mine.
So, what are fixtures for?
According to the docs, Cypress Fixtures “Load a fixed set of data located in a file.” Okay. That’s what they do, but I need to understand what they are for. Here’s what helped me.
TL;DR: Fixtures are sets of reusable test data that can be created and used in any environment that returns reliable test results when the tests are run.
Think about it this way. Our tests need stability. Flaky test results cause mistrust within the team. What causes flakiness? Lots of things but not having reliable test data is one of them. We need the same test data that the tests can run against over and over and reliably report correct results (pass or fail). Fixtures are static data that can be transferred to any environment. Remember, we aren’t testing the data - we’re testing functionality. So, as long as the data is the same, we can use our tests in any environment and get reliable results that maintain trust in the tests and each other.
Nice! So, how do I create a fixture?
This is where I found a decent majority of the internet tutorials lacking. They would start with tests that just worked with pre-existing feature files. It all worked, everything worked, yay 🎉 Except, when following the tutorials, the steps weren’t working for me and I didn’t know how to troubleshoot. This is where learning how to create a fixture helped me the most.
Chip Cullen’s explanation on “How to Create and use Fixtures in Cypress Tests” brought it home for me. While I won’t go through each step outlined there, here are updated steps that can be used at the time of this writing:
After identifying which XHR request you need to use for your tests, follow Step 1 he outlines in his article:
Create a test that will write the XHR response to your fixtures folder. For this example, we’ll use a login request:
// cypress/e2e/fixtureSpec.cy.ts it("writes the fixtures file and stores the response in the fixtures folder", () => { cy.request("POST", "/auth/login", { email: "emailexample@emailtest.com", password: "PasswordExample" }).then((response) => { cy.writeFile("cypress/fixtures/login.json, response.body) }) })
Run Cypress
💥 You now have a login.json fixture file in your fixtures folder! This is the file we’ll use to stub the login response. Continuing on to…
Use
cy.intercept()
to stub and use the responsecy.intercept("POST", "auth/login"), "fixtures:login.json");
For example: I needed to use this stubbed response to simulate logins and access different parts of the application. Here’s what it looks like in a command.
// cypress/support/commands.ts
Cypress.Commands.add("loginStub", (email, password) => {
cy.intercept("POST", "auth/login"), "fixtures:login.json");
cy.login(email, password)
});
Now, when I need to log in, but don’t want to do that over and over again, creating slower tests, I can just pass cy.loginStub(email, password)
in a beforeEach!
Okay, that’s cool. How do I access the values in my fixtures file?
Let’s say you have a fixture file that contains your users:
// cypress/fixtures/users.json
{
"users": {
"email": "emailexample@emailtest.com",
"password": "PasswordExample"
}
}
Back in our spec file, you can do the bare minimum, just to make sure it’s working and you understand the concepts:
// cypress/e2e/spec.cy.ts
import { users } from "cypress/supposrt/fixtures/users.json
describe("fixture test", () => {
it("loads the user from the fixture file", () => {
cy.get("[aria-label='example text field']").type(users.name)
})
})
Run the test and 😱 it works!
I put this question to the Cypress community. “Can someone explain to me why I need to use cy.fixture
over an import that doesn’t require it at all? And, would something like the above be appropriate for using?”
Here’s one user’s response:
Loading a fixture file with an import at the beginning of your spec file, as your example, is simply one of the ways to do it.
Using cy.fixture() within your test methods is another way of loading a fixture file, but be aware that in that case you will need to run cy.fixture() within an .it() or a hook like beforeEach().
With the import, you are storing the content of the file in a global variable as a regular javascript object. With the cy.fixture() you usually store what you load in a Cypress alias.
How you reference/access that content is what is different (simple js variable VS alias). Note that a Cypress alias is a chainable.
They then recommended Gleb Bahmutov’s excellent post: “Import Cypress Fixtures”
My favorite part though was this:
“simply one of the ways to do it.”
Too often we get tied up in thinking there is only one way to accomplish something. Sometimes, if we take a step back and approach the problem from another angle, we will see what we need to see and make decisions from that vantage point.
Now, go forth and import those fixtures!
Till next time…
Sources discussed in this article:
Cypress Fixtures: https://docs.cypress.io/api/commands/fixture
How to Create and use Fixtures in Cypress Tests: https://chipcullen.com/how-to-create-and-use-fixtures-in-cypress-tests/
Import Cypress Fixtures: https://glebbahmutov.com/blog/import-cypress-fixtures/