Code Monkey home page Code Monkey logo

miragejs's Introduction

Mirage JS

npm version example workflow

A client-side server to develop, test and prototype your JavaScript app.

Documentation

Visit miragejs.com to read the docs.

miragejs's People

Contributors

2468ben avatar alecho avatar asantos00 avatar azdaroth avatar bantic avatar bekzod avatar bendemboski avatar blimmer avatar cibernox avatar dependabot-preview[bot] avatar dependabot-support avatar dependabot[bot] avatar dfreeman avatar ef4 avatar geekygrappler avatar ianvs avatar jherdman avatar jrjohnson avatar lazybensch avatar lolmaus avatar lukemelia avatar makepanic avatar mupkoo avatar offirgolan avatar rjschie avatar rwjblue avatar ryanto avatar samselikoff avatar turbo87 avatar willrax avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

miragejs's Issues

How to use it with nuxt axios ?

I have action

export const actions = {
  async getLocales({commit}) {
    console.log('123');

    const { locales } = await this.$axios.$get('/api/getlocales');

    if (locales) {
      commit('setLocales', locales);
    }
  }
};

I try write test.

import {actions} from '../../../store/lang';
import axios from 'axios';
import { Server } from "miragejs"

let server;

beforeEach(() => {
  server = new Server({
    routes() {
      this.namespace = "/api";

      this.get("/getlocales", () => ({
        locales: ['en', 'ru']
      }))
    },
  })
});

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

let action;

const testedAction = (context = {}, payload = {}) => {
  return actions[action].bind({$axios: axios})(context, payload);
};

describe(`store`, () => {
  describe(`actions`, () => {
    describe(action = 'getLocales', () => {
      it(`returns an empty array if axios returns an empty array`, async (done) => {
        expect(await testedAction()).toEqual({locales: ['en', 'ru']});
        done();
      });
    });
  });
});

I have error with

    TypeError: this.$axios.$get is not a function

      18 |     console.log('123');
      19 | 
    > 20 |     const { locales } = await this.$axios.$get('/api/getlocales');
      21 | 
      22 |     if (locales) {
      23 |       commit('setLocales', locales);

How to fix it ?

Pass server to as parameter to config functions in addition to `this` context.

Whenever you start getting into making higher order functions in order to create routes, you inevitably run afoul of using this context.

This problem is easy to work around, but not in a way that doesn't introduce some ugliness.

I'd suggest passing, in addition to the this context, the server to the routes() function. This not only makes higher order functions easier, but would also bring it in line with the scenario api.

Node support tracking issue

This issue is to track the work needed for making Mirage's server runnable in Node. I want to write up our current thinking so that we can start to solicit feedback.

Mirage works by intercepting browser calls to fetch and XMLHttpRequst and returning a mocked HTTP response. For Node environments the approach we are taking is a bit different. Instead of intercepting all outgoing network requests from Node we will be providing a lower level APIs that allow you generate Mirage responses in user land.

For example, there will be a server.handleRequest({ method: 'GET', path: '/articles', ... }) function to generate a Mirage response for a GET /articles.

The reason for this approach is that it allows you to use a Mirage response however you see fit. It can be served from an express server or used with a node mocking library like nock. Because the use cases for running Mirage in node are much more diverse than the browser, we feel providing these lower level APIs is a good first step.

The first APIs we want to add to server:

  • canHandle(request): boolean
  • handleRequest(request): response

At this time we are unsure about the shape of the request/response objects. We will most likely look to current Mirage/Pretender and use these objects as a starting point.

Any feedback would be greatly appreciated! Thanks

Not able to test error states with Cypress

I have followed below link to setup miragejs with Cypress.

https://miragejs.com/quickstarts/cypress/setup

Issue: Setup doesn't work for error responses.

When I add below code in my test.

  /* skipping this test for now becuase miragjs,
   doesn't return error response as expected. */
  server.del('/users/1', () => {
    return new Response(400, {}, { message: 'Invalid input' });
  });

I actually receive below response in my application code (My application is using plain fetch , no axios).

To my surprise response from fetch has status as 200 but has payload as my error response.

{"type":"default","status":200,"ok":true,"statusText":"OK","headers":{"map":{"content-type":"application/json"}},"url":"https://localhost:3030/stores/1","_bodyInit":"{\"message\":\"Invalid input\"}","_bodyText":"{\"message\":\"Invalid input\"}"}

Can someone help me to solve this, I am blocked.

loadFixtures() should throw on missing file

Extracted from miragejs/ember-cli-mirage#1698

How to reproduce

Run server.loadFixtures('countries') with a non-existing fixture file name

Expected behavior

It should crash with a meaningful error message, indicating that you attempted to include fixtures that don't exist.

Actual behavior

It passes silently, making you think that fixtures were loaded, but they didn't. Your tests work and you'd waste time looking for an error elsewhere before you realize fixtures have not been loaded.

Shorthand route fails if it has a trailing slash

In our app we had a shorthand route set up as this.get('/notifications/'); which broke our tests when upgrading from ember-cli-mirage 1.1.3 to 1.1.4 with the following error:

"Mirage: The route handler for http://localhost:4200/ghost/api/v3/admin/notifications/ is trying to access the model, but that model doesn't exist."

Digging in, it appears that getModelClassFromPath() is failing when the path has a trailing slash:

Screenshot 2019-10-28 at 15 35 27

Relationships not yielding results

I have the following fake API:

import { Server, Model, Factory, belongsTo, hasMany } from 'miragejs'
import faker from 'faker'

new Server({
  models: {
    company: Model.extend({ jobs: hasMany() }),
    job: Model.extend({ company: belongsTo() })
  },

  routes() {
    this.namespace = 'api'

    this.get('/companies')
    this.get('/companies/:id')

    this.get('/jobs')
    this.get('/jobs/:id')
  },

  factories: {
    company: Factory.extend({
      name: faker.company.name,
      logo: faker.company.logo,
    }),

    job: Factory.extend({
      title: () => faker.name.jobTitle(),
      createdAt: () => faker.date.past(),
      location: () => `${faker.address.city()}, ${faker.address.country()}`,
      body: () => `${faker.lorem.paragraphs(3)}
- ${faker.lorem.sentence()}
- ${faker.lorem.sentence()}
- ${faker.lorem.sentence()}
- ${faker.lorem.sentence()}
- ${faker.lorem.sentence()}

${faker.lorem.paragraphs(2)}
      `
    })
  },

  seeds(server) {
    server.createList('company', 5).forEach(company => server.createList('job', 10, { company }))
  }
})

When I do GET /api/companies I get one result back: { id: "1" }. I'm expecting 5 companies back. Also when doing GET /api/job/1?include=company I never get the company back in the result, but I get the rest of the fake information.

Any idea what's going on here with my company model? It's simply not working.

Better default passthrough behavior

Right now, if you

  1. Create a new react app
  2. Install and start mirage
  3. Write a ESLint error somewhere (in non-Mirage code)

then you'll see this

image

The overlay normally lets you click on the error and it will open your editor to the problematic line. Mirage's intercepting behavior breaks this.

We've also seen plenty of other situations where Mirage's default intercepting behavior gets in the way of things like hot module reloading.

I think, ideally if we had some sort of routing dsl, we could scope Mirage's intercepting behavior to a namespace. Something like

routes() {
  this.path('/api', () => {
    this.get('/users', () => {});
  })
}

and then Mirage would be able to throw an error about undefined route handlers for requests made to /api/*.

Anyways, we don't need to figure out that feature now but we should strongly consider changing Mirage's default passthrough behavior to allow all requests through, I think.

Verify ie11

If we're committing support for IE11 it'd be nice to have some test coverage for it, somehow

#36

React Native support

React Native's network stack is pretty compatible with the browser's so I don't think it would be much of a stretch to officially support React Native.

I was able to get a minimal example working with a few tweaks:

  1. Mirage and Pretender both assume self is available. Using RN-browser-polyfill to expose global as self before importing mirage fixed this. We could guard against this, but the polyfill seems to be a common enough solution for this in RN.
  2. Pretender uses document.createElement('a') to parse URLs which isn't available in mobile. I "fixed" this by patching mirage's umd'd parseUrl to fall back to url-parse. This should be fixed upstream or we can at least move the patch to mirage itself.

@samselikoff mentioned some de-pretenderingthat's happening for node support, so that may also be an option 💯

Performance tests should actually assert something or being skipped

There are some (very basic) performance tests in __tests__/internal/integration/performance/simple-factory-test.js. Currently they don't assert anything cause don't pass a timeout (optional forth parameter) to perfTest utility. Should add a reasonable timeout or skip them all together to not waste time.

https://github.com/miragejs/server/blob/c181646bb5004b12127b90390d93f4cdb9ef9e06/__tests__/internal/integration/performance/simple-factory-test.js#L33-L35

https://github.com/miragejs/server/blob/c181646bb5004b12127b90390d93f4cdb9ef9e06/__tests__/internal/integration/performance/utils.js#L1-L13

Deactivate mirage without removing from the compiled web app

I'm experimenting with Mirage in an Angular app. Mirage is compiled in the main application. I noticed that Mirage is active automatically without calling it directly, so it redirects all AJAX request automatically.

As I see, in Ember Application it is removed from the final build when Mirage feature is turned off in production build.

Is there any option to keep Mirage in the codebase, but turning it off programatically?

Can't get mirage to work when running cypress run

Hey all!

I've been integrating mirage into our react + cypress app.

Everything worked great when running cypress open (running the tests inside a real browser controlled by cypress) as long as I added this line at the top of my test file:

Cypress.on('window:before:load', win => {
  win.XMLHttpRequest = window.XMLHttpRequest
})

But, when running the cypress run command, our tests run in an electron browser and it seems like mirage is unable to swap the xhr requests with the mirage routes.

Would it be possible to get some assistance in figuring out how to overcome this problem?
Thanks so much for this wonderful piece of software!

Mirage error getting swallowed

This is an illegal RestSerializer configuration (should fix that, seems bad...) but there's no error and the promise is not thrown either.

import React, { useState, useEffect } from "react";
import { Server, RestSerializer, Model } from "@miragejs/server";

new Server({
  models: {
    todo: Model
  },
  serializers: {
    application: RestSerializer.extend({
      root: false
    })
  },
  baseConfig() {
    this.namespace = "api";
    this.resource("todo");
  },
  scenarios: {
    default(server) {
      server.create("todo");
    }
  }
});

function App() {
  let reloadApp = () => {
    window.location.reload();
  };

  let [todos, setTodos] = useState([]);

  useEffect(() => {
    fetch("/api/todos")
      .then(() => {
        console.log("hi");
      })
      .catch(() => {
        console.log("ho");
      });
    // .then(response => response.json())
    // .then(json => setTodos(json))
    // .catch(() => {
    //   console.log("here");
    // })
    // .finally(() => {
    //   console.log("hi");
    // });
  }, []);

  return (
    <div>
    </div>
  );
}

Potential orm bug

I think there's an orm bug...

// post
import DS from 'ember-data';

export default DS.Model.extend({

  author: DS.belongsTo(),

  owner: DS.belongsTo('ownerable', { polymorphic: true, inverse: null })

});


// author
Model.extend({
  
  post: belongsTo()
  
});

// scenario
let post = this.server.create('post', {
  owner: this.server.create('author')
});

author should get set as the post's owner, but ends up getting set as post's author. (The owner relationship has a null inverse, but I believe when author is being saved it looks to see if there's an inverse for model type post, says yes there is, then updates everything.)

What is the expected visibility of associationIdKeys and associationKeys

Bug or question

I have a custom serializer which makes use of associationIdKeys and associationKeys to map the id attribute found in attrs back to the name of the key which is used to access the associated model. This was broken when these properties were changed to Set instead of Array instances (#151), which is fine, but it makes me wonder if our custom serializer should not be using those fields (they are not prefixed with underscore, but they are also not explicitly documented). Is there a better way to achieve what we are doing. For a very basic example of the type of thing we were doing, consider this snippet:

const customSerializedResult = Object.keys(modelInstance.attrs).reduce((result, currKey) => {
  if (modelInstance.associationIdKeys.includes(currKey)) {
    // ... get associationKey and retrieve nested model to serialize it
  } 
  
  // ... handle top level property
});

Is there a different or better way to be doing this?

Updating relationship to models by assigning behaves different from using .create or .update

Use case outlined in this gist: https://gist.github.com/ohcibi/95e66307e7a0ce80fa9d34a5e4be01f5

If I assign some models using = to another model the relationship in the schema seems not to be updated properly. Using .save() or not makes no difference. When assigning the related models in an object passed to .create() or .update() the relationship gets properly updated immediately.

Update

I created an example project that reproduces the error: https://github.com/ohcibi/mirage-schema-bug-demo

Mirage cannot include non-camel cased model attributes when using json-api-serializer

https://github.com/miragejs/miragejs/blob/master/lib/serializers/json-api-serializer.js#L450

Hopefully I'm not misunderstanding something here as I am new to mirage, but the line above explicitly transforms included relationship keys into camel case before checking for their presence on a given model. The attributes on our models are both snake and camel case so this line causes an error when queries attempt to include snake_cased keys. Overriding this function to check for snake and camel cased attributes has resolved our issues for now but perhaps it makes sense to allow for this behavior by default.

Unable to run Application Tests using Vue CLI + Cypress + Mirage

Hey there!

I'm spinning up a new Vue app and wanted to use Mirage to mock out my API but I'm having issues getting the Application tests to run in Cypress.

If I start the Mirage server in my src/main.js file, I get a response and all works as expected.

If I start the Mirage server in my Cypress test (tests/e2e/specs/test.js), I get a 404 response so I don't think Mirage is intercepting the request as expected, but I can't figure out why.

I've put together a repo with the test from the Our First Test section of the Mirage documentation.

Would it be possible to get some assistance to figure out what's not working here? Hopefully I've just missed something simple!

Better Lodash usage

In #77 we pulled in individual lodash packages instead of lodash-es but it's kinda lousy dev experience. Would love to get our setup better so we can just use lodash-es but get tree-shaking working!

Inverse Error When There Should be None

Given:

class User extends Model {
  @belongsTo('avatar', { async: false }) avatar;
}

class Avatar extends Model {
  @belongsTo('user') user;
}

Every now and then I get this error:

Mirage: Error: The user model has multiple possible inverse associations for the avatar.user association.

This is a head scratcher...

add hasOne / belongsToMany as aliases of belongsTo / hasMany

@samselikoff , would you take a PR adding hasOne & belongsToMany relationships, that would just behave like aliases of belongsTo / hasMany in Mirage?

=> running some scripts on our domain model here, and need to order by child / parent so useful to specify in the relationships if only for doc purposes (I also believe there may be some room to destroy children as opposed to parents when deleting a model in some cases in Mirage but that would not be part of this PR).

Document seeds in Server

They're missing from the API docs.

Right now Server still uses config.scenarios, we should refactor to use this.seeds like other properties, then properly document it.

Get Jest to run eslint

Tried setting this up, but jest eslint runner disagreed with what eslint from the CLI did (which was the same as what my editor did).

It does seem to be reading .eslintrc.js but for some reason disagrees? Not sure

Add delay to Model mutation methods

I want to test scenarios where database writes take longer than expected. For example, after saving a piece of data, then navigate to a dashboard to view. If writing to database takes a long time, the dashboard might not be able to retrieve the newly added data.

If I use "timing" config, it only delay the response but writing to Mirage DB still happens in real time. Can you add a timing config to db mutation methods e.g. create, update, save?

Mirage is not re-patching fetch/XMLHttpRequest after hot reloading

While using @miragejs/server with a vuejs app, I encountered a issue that happens after live-reloading.

The fetch function was being hot reloaded after changing the code and (I suppose) it was not getting re-patched by pretenderjs and thus the newly defined routes weren't being detected.

After a proper refresh, they worked.

This is a pretty common usecase for react/vuejs apps and so it looks like we wanna fix it.

Tricky situations with traits/afterCreates & Factory API

In #187 @krasnoukhov raised some clunky parts of the current Factory API.

Let's say we have the following models:

// models/comment.js
export default Model.extend({
  post: belongsTo()
})

// models/post.js
export default Model.extend({
  comments: hasMany()
})

A comment always has a post, so we define the following comment factory:

// factories/comment.js
export default Factory.extend({
  afterCreate(comment, server) {
    comment.update({
      post: server.create('post')
    })
  }
})

That way in tests, we can write server.create('comment') and always get a valid comment (i.e. one that has a post associated with it).

But we also want to be able to override the default post on our own at the calling site, so we can do this:

let publishedPost = server.create('post', { published: true })
server.create('comment', { post: publishedPost });

Right now this won't work, because the afterCreate hook on our factory definition always gets called after the server.create command is finished. So we'll override any overrides with our comment.update command in afterCreate.

To fix this, we can add some logic to our afterCreate hook:

// factories/comment.js
export default Factory.extend({
  afterCreate(comment, server) {
    if (!comment.post) {
      comment.update({
        post: server.create('post')
      })
    }
  }
})

The hook now first checks to see if the comment has an associated post, and only creates one if it doesn't.

Now our callers can do

server.create('comment') // gets the default post
server.create('comment', { post: publishedPost }) // gets our overridden post

and get what they expect. (Note that this is what the association() helper does by default. Note also it only works for belongsTo relationships.)

Here's the wrinkle. Let's say we add a trait.

// factories/comment.js
export default Factory.extend({
  afterCreate(comment, server) {
    if (!comment.post) {
      comment.update({
        post: server.create('post')
      })
    }
  },

  withPublishedPost: trait({
    afterCreate(comment, server) {
      if (!comment.post) {
        comment.update({
          post: server.create('post', { published: true })
        })
      }
    }
  })
})

If we wrote it like this, server.create('comment', 'withPublishedPost') actually wouldn't work as we expect. That's because the root afterCreate runs first, which creates the post. Then when the trait's afterCreate runs, the comment has a post, so comment.post is truthy and our extra code never runs.

So, we could omit the check from our trait's afterCreate and write it like this:

// factories/comment.js
export default Factory.extend({
  afterCreate(comment, server) {
    if (!comment.post) {
      comment.update({
        post: server.create('post')
      })
    }
  },

  withPublishedPost: trait({
    afterCreate(comment, server) {
      comment.update({
        post: server.create('post', { published: true })
      })
    }
  })
})

but then we've introduced two new problems. First, server.create('comment', 'withPublishedPost') now creates two posts, one from the root afterCreate and another from the withPublishedPost trait. Even though the comment is correctly associated with the second one, it's less than ideal that an extra post is created for no reason.

Second, this usage won't work as expected:

server.create('comment', 'withPublishedPost', { post: myTestsPublishedPost })

because again, the afterCreate hook runs after the rest of the server.create logic has been resolved.

The only real escape hatch today is to do something like this:

let comment = server.create('comment', 'withPublishedPost')
comment.update({ post: myTestsPublishedPost });

but this is pretty awkward, and moreover we've now created 3 posts when we only really wanted 1.

(FWIW this example is a bit contrived, as one could reasonably ask, "Why use the withPublishedPost trait if you're just going to be overriding the post? Couldn't you just omit the trait then the override would work as expected?" But you can imagine a trait that does more work than this, and that a test writer would want to use, apart from some relationship that they want to override.)


So that's the problem, and we need to address it!

Need to be able to treat collection as array

Collection exposes one array method length, but a common pattern is to clone the array of data by calling toArray() on the collection of models. This is not currently possible since the models array is nested inside of Collection.

Can/will you expose other array methods so a collection can be treated as an array?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.