vue.jsautomated-testscypressvue-climirage

Mirage undefined error (no route defined) when testing with Vue and Cypress


I have Cypress running in a Vue CLI App and recently added Mirage to extend the mocking of my database. I followed Mirage's quickstart tutorial to use it in Cypress and now I am trying to re-write my login test. The login in the application works with a POST request to the API endpoint /oauth/token, but in Cypress/Mirage it fails saying

"Mirage: Your app tried to POST 'http://localhost:8090/oauth/token', but there was no route defined to handle this request. Define a route for this endpoint in your routes() config. Did you forget to define a namespace?"

It seems as if the routes from the routes() hook in server.js are not registered to the server:

import { Server, Model } from 'miragejs'

export function makeServer({ environment = 'development' } = {}) {
  let server = new Server({
    environment,

    models: {
      user: Model,
    },

    seeds(server) {
      server.create("user", { name: "Bob" })
      server.create("user", { name: "Alice" })
    },

    routes() {
      this.urlPrefix = 'http://localhost:8090'
      this.namespace = ''

      /* Login */
      this.post("/oauth/token", () => {
        return { 'access_token': 'abcd123456789', 'token_type': 'bearer', 'refresh_token': 'efgh123456789'}
      })
    }
  })

  return server
}

In the spec file's beforeEach hook I call the server function:

import { makeServer } from '../../src/server'

let server

beforeEach(() => {
  server = makeServer({ environment: 'development' })
})

And I also added this block to cypress/support/index.js as in the tutorial:

Cypress.on("window:before:load", (win) => {
  win.handleFromCypress = function (request) {
    return fetch(request.url, {
      method: request.method,
      headers: request.requestHeaders,
      body: request.requestBody,
    }).then((res) => {
      let content =
        res.headers.map["content-type"] === "application/json"
          ? res.json()
          : res.text()
      return new Promise((resolve) => {
        content.then((body) => resolve([res.status, res.headers, body]))
      })
    })
  }
})

And I added this block to Vue's main.js:

import { Server, Response } from "miragejs"

if (window.Cypress) {
  new Server({
    environment: "test",
    routes() {
      let methods = ["get", "put", "patch", "post", "delete"]
      methods.forEach((method) => {
        this[method]("/*", async (schema, request) => {
          let [status, headers, body] = await window.handleFromCypress(request)
          return new Response(status, headers, body)
        })
      })
    },
  })
}

The environment 'test' in main.js does not make a difference if I change it to 'development'.

Is there any way to see which routes are registered to my server at any point of the server runtime? When debugging the server in my spec, the routes attribute of the server has a length of 0. Did I define my routes at the wrong time or place?

UPDATE: I have figured out that I can use a working Mirage route in my local web app when I make the server in Vue's main.js as described here instead of using the Cypress framework. So now I think the route definition is fine and the problem must be inside the code for Cypress' request interception.


Solution

  • I finally found the solution with the help of Sam Selikoff on Mirage's discord channel. My problem was that my API is running on a different port than my application.

    As Sam stated on discord (I rephrased it a little):

    By default this.passthrough() only works for requests on the current domain. The code from Step 4 of the quickstart in Vue's main.js needs to say

    this[method]("http://localhost:8090/*", async...
    

    instead of

    this[method]("/*", async...
    

    I hope this will help someone in the future. It cost me a whole week.