Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add migration guide from cypress fixtures to mirage #193

Open
romansndlr opened this issue Dec 30, 2019 · 3 comments
Open

Add migration guide from cypress fixtures to mirage #193

romansndlr opened this issue Dec 30, 2019 · 3 comments

Comments

@romansndlr
Copy link

Hey all!

Why?
Because this is a very big issue for cypress developers...

When using cypress fixtures you are required to write out static JSON files in order to have mocked out responses.

I think this could be a huge selling point for mirage because you could swap using these static very limited in power JSON files to use the full power of a browser database with really minimal work.

How
So generally in order to mock out an HTTP request with cypress you would do something like this:

// createUser.json
{
    "id": 1,
    "firstName": "John",
    "lstName": "Doe"
}

// create-user.spec.js
it('Should create a new user', () => {
    // Lets assume we have an endpoint for creating a user
    // And we stub it out by telling cypress to use a fixture called createUser.json
    cy.server()
    cy.route({
      method: 'POST',
      url: '/users',
      response: 'fixture:createUser.json'
    }).as('createUser')

    // We define the values we want to enter into the inputs
    const FIRST_NAME = 'John'
    const LAST_NAME = 'Doe'

    // We find the form on the page, type in our predefined values and submit the form
    cy.get('form')
      .within(() => {
        cy.get('input[name="first_name"]').type(FIRST_NAME)
        cy.get('input[name="last_name"]').type(FIRST_NAME)
      })
      .submit()

    // We wait our app to send the request we stubbed out
    cy.wait('@createUser').then(({ response, request }) => {
      // and assert that the values that where sent to the server are the ones we typed in
      expect(request.body.firstName).to.equal(FIRST_NAME)
      expect(request.body.lastName).to.equal(LAST_NAME)

      // We want to make sure we are redirected to the correct url using the id that was created for the user
      cy.location().should(loc => {
        expect(loc.pathname).to.eq(`/users/${response.body.id}`)
      })

      // We want to make sure that the form is populated with the values that were returned from our api
      cy.get('form').within(() => {
        cy.get('input[name="first_name"]').should('have.value', FIRST_NAME)
        cy.get('input[name="last_name"]').should('have.value', LAST_NAME)
      })
    })
  })

The problem here is that if we ever change John Doe to be Jane Doe, this test would fail because the fixture JSON file doesn't change.

Luckily, not much is required to change this test to use mirage instead:

// server.js
import { Server } from 'miragejs'

new Server({
  routes() {
    this.post('/users', (schema, request) => {
      const user = JSON.parse(request.requestBody)
      return schema.users.create(user)
    })
  }
})

// create-user.spec.js
import { makeServer } from "../../src/server"

let server

beforeEach(() => {
  server = makeServer({ environment: "test" })
})

afterEach(() => {
  server.shutdown()
})

it('Should create a new user', () => {
    // Lets assume we have an endpoint for creating a user
    // And we stub it out by telling cypress to use a fixture called createUser.json
    cy.server()
    cy.route({
      method: 'POST',
      url: '/users'
      // Remove 👇👇👇 this line to tell cypress to let the request through
      // response: 'fixture:createUser.json'
      // Now we can change the user's name to anything and the test will still pass
    }).as('createUser')

    // We define the values we want to enter into the inputs
    const FIRST_NAME = 'John'
    const LAST_NAME = 'Doe'

    // We find the form on the page, type in our predefined values and submit the form
    cy.get('form')
      .within(() => {
        cy.get('input[name="first_name"]').type(FIRST_NAME)
        cy.get('input[name="last_name"]').type(FIRST_NAME)
      })
      .submit()

    // We wait our app to send the request we stubbed out
    cy.wait('@createUser').then(({ response, request }) => {
      // and assert that the values that where sent to the server are the ones we typed in
      expect(request.body.firstName).to.equal(FIRST_NAME)
      expect(request.body.lastName).to.equal(LAST_NAME)

      // We want to make sure we are redirected to the correct url using the id that was created for the user
      cy.location().should(loc => {
        expect(loc.pathname).to.eq(`/users/${response.body.id}`)
      })

      // We want to make sure that the form is populated with the values that were returned from our api
      cy.get('form').within(() => {
        cy.get('input[name="first_name"]').should('have.value', FIRST_NAME)
        cy.get('input[name="last_name"]').should('have.value', LAST_NAME)
      })
    })
  })  

So basiclly all we had to do to make the transition to mirage is define the route handler (which you need to do regradless) and remove one line from our test file.

@ryanto
Copy link
Collaborator

ryanto commented Jan 28, 2020

Hey @romansndlr thanks for this!

Now that miragejs.com is launched I'd like to start getting more guides like this onto the website. This is much appreciated, especially since you have a lot of Cypress experience!

I think there's a lot of power behind a guide that shows you how to move away from Cypress static fixtures and start using Mirage factories. I'm wondering if there are any Cypress tests you've written that have a lot of repetitive fixtures that are easier expressed as a factory?

Also, Sam and I have been recommending folks not use @alias's with Mirage requests because it feels like testing implementation details. Sam wrote about this here. Do you think it's ok to cut the cy.wait('@createUser') from the example?

@samselikoff Would like to get your thoughts here as well.

@samselikoff
Copy link
Contributor

samselikoff commented Jan 28, 2020 via email

@romansndlr
Copy link
Author

Hey, @ryanto @samselikoff! It is my pleasure to contribute to this wonderful tool!

About the waiting for the HTTP request, we might have a disagreement about this issue for these reasons:

  • This is typically the types of assertions that cypress users have because this is encouraged by the cypress docs, so if we want to provide the easiest "least resistance" path to migrate a test file from fixtures to mirage, I do think we should keep it just to show you don't have to remove it.
  • From my experience, most of the time you are not 100% covered by just asserting against the rendered output of a successful HTTP request... sometimes you need to create a user and then you are redirected to the users' list that doesn't display all the properties you passed in the request, or you need to notify the server to send out an email and you need to make sure the right data is sent.
  • If we are recommending against this approach, I think we should find an alternative way to make sure the right data is sent to the server when you need it. do you guys still recommend asserting against mirage's database?
  • Another thing to keep in mind is that users might want to change a test from being an integration test to an e2e test so any guide we provide should also take this into consideration.

So let me know what you guys think and then I can try and make a new draft of something more robust. maybe we can make a multistep guide that shows different levels of migration, meaning you show the smallest change you have to do and also how to take it a step forward and make a bigger change.

Thanks again for the opportunity! still waiting on that invite to the podcast though 😜

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants