Collections
Description
- A
collection
of route variants defines all current routes with their correspondent variant in the API mock. The user can choose which collection has to be used on each particular moment. - They can be created extending other collections. So, you can store many collections and change the whole API behavior by simply changing the current one.
- Plugins can provide ways of creating collections automatically. For example, the
openapi
plugin creates collections of routes from OpenAPI documents.
So, basically, when you define a collection, you are saying to the server:
"When this route is requested send the response defined on its variant X, for that one use the response defined on its variant Y, etc."
The management of different responses for the same route and the ability to store different collections allows to easily toggle between many predefined API state simulations without having to modify any code. This is great while developing an API client, because all of the responses of the API can be changed at a time using the interactive CLI. And it is also great while running tests, because the different collections can be used to test different scenarios.
Load
- Usually, collections must be defined in the
mocks/collections.js
file of your project. You can also use a.json
or a.yaml
file, or event other formats using Babel. That file must export an array of collection (or a function returning an array of collections). Read Organizing files for further info. - Collections can also be loaded programmatically using the JavaScript API.
- JS file
- JSON file
- YAML file
- TypeScript file
- JavaScript API
project-root/
├── mocks/
│ ├── routes/
│ │ ├── common.js
│ │ ├── books.js
│ │ └── users.js
│ └── collections.js <- DEFINE YOUR COLLECTIONS HERE
└── mocks.config.js
project-root/
├── mocks/
│ ├── routes/
│ │ ├── common.js
│ │ ├── books.js
│ │ └── users.js
│ └── collections.json <- DEFINE YOUR COLLECTIONS HERE
└── mocks.config.js
project-root/
├── mocks/
│ ├── routes/
│ │ ├── common.js
│ │ ├── books.js
│ │ └── users.js
│ └── collections.yml <- DEFINE YOUR COLLECTIONS HERE
└── mocks.config.js
project-root/
├── mocks/
│ ├── routes/
│ │ ├── common.ts
│ │ ├── books.ts
│ │ └── users.ts
│ └── collections.ts <- DEFINE YOUR COLLECTIONS HERE
└── mocks.config.js
Read the using Babel guide for further info about how to use TypeScript.
const { createServer } = require("@mocks-server/main");
const { routes, collections } = require("./fixtures");
const core = createServer();
core.start().then(() => {
const { loadRoutes, loadCollections } = core.mock.createLoaders();
loadRoutes(routes);
loadCollections(collections);
});
Check out the Openapi integration chapter to learn how to create routes and collections automatically from OpenAPI documents 🎉
Format
Collections must be defined as an array of objects containing:
id
(String): Identifier for the collection.from
(String): Optional. Collection id from which this collection extends.routes
(Array of Strings): Route ids and the variant id to be used by each one of them, expressed in the format[routeId]:[variantId]
- Json
- Yaml
- JavaScript
- Async JS
[
{
"id": "base", // collection id
"routes": ["get-users:all", "get-user:id-1"] // collection routes
},
{
"id": "user-2", // collection id
"from": "base", // extends "base" collection
"routes": ["get-user:id-2"] // "get-user" route uses "id-2" variant instead of "id-1"
}
]
- id: "base" # collection id
routes: # collection routes
- "get-users:all"
- "get-user:id-1"
- id: "user-2" # collection id
from: "base" # extends "base" collection
routes:
- "get-user:id-2" # "get-user" route uses "id-2" variant instead of "id-1"
module.exports = [
{
id: "base", // collection id
routes: ["get-users:all", "get-user:id-1"] // collection routes
},
{
id: "user-2", // collection id
from: "base", // extends "base" collection
routes: ["get-user:id-2"] // "get-user" route uses "id-2" variant instead of "id-1"
}
];
const { getBaseRoutes } = require("./helpers");
module.exports = async function() {
const baseRoutes = await getBaseRoutes();
return [
{
id: "base", // collection id
routes: baseRoutes // collection routes
},
{
id: "user-2", // collection id
from: "base", // extends "base" collection
routes: ["get-user:id-2"] // "get-user" route uses "id-2" variant instead of "id-1"
}
];
}
Changing the current collection
You can use the configuration or any of the available APIs or integration tools to change the current collection while the server is running:
- Configuration
- NodeJS
- Interactive CLI
- Cypress
- REST API
module.exports = {
mock: {
collections: {
selected: "collection-b",
},
},
};
const { createServer } = require("@mocks-server/main");
const core = createServer();
core.start().then(() => {
core.mock.collections.select("collection-b");
});
Select "Select collection" -> press "Enter" -> Select the collection you want for the server to use -> press "Enter".
describe("books page", () => {
describe("when there are two books", () => {
before(() => {
cy.mocksSetCollection("collection-b"); // Change collection to "collection-b"
cy.visit("/");
});
it("should display two users", () => {
cy.get("#users li").should("have.length", 2);
});
});
});
curl -X PATCH -d '{"routes":{"collections":{"selected":"collection-b"}}}' -H 'Content-Type: application/json' http://localhost:3200/admin/settings
Defining custom route variants
Mocks Server provides a feature to change the variant of routes defined in the current collection while the server is running without the need to create another collection nor modify any code. So, you can modify the current collection route variants on the fly.
For example, if the current collection defines that for the route-A
it uses the variant-B
("routes": ["route-A:variant-B"]
), you can change it temporarily to return the variant-C
using any of the available APIs or integration tools. When doing so, the current collection will be modified in memory, and it will be restored whenever another collection is selected or routes or collections are reloaded.
This is very useful when you need to change the response of only one route temporarily, because you don't need to create another collection, nor modify an existent one.
- NodeJS
- Cypress
- REST API
- Interactive CLI
const { createServer } = require("@mocks-server/main");
const core = createServer();
core.start().then(() => {
core.mock.collections.select("collection-b");
core.mock.useRouteVariant("route-A:variant-C")
});
describe("books page", () => {
describe("when there are two books", () => {
before(() => {
cy.mocksUseRouteVariant("get-books:two");
cy.visit("/");
});
after(() => {
cy.mocksRestoreRouteVariants();
});
it("should display two users", () => {
cy.get("#users li").should("have.length", 2);
});
});
});
curl -X POST -d '{"id": "get-user:2"}' -H 'Content-Type: application/json' http://localhost:3110/api/mock/custom-route-variants
Select "Use route variant" -> press "Enter" -> Select the route variant you want for the collection to use -> press "Enter".
Custom variants for the current collections are lost whenever another collection is selected or a change is made in files. If you need to set that scenario frequently, then you should consider creating a collection instead.
Extending collections
Collections can be created extending another one. This means that you can get all of the routes defined in one collection, and create another collection changing only the variants of some of its routes.
This is specially important when an API contains many resources, because you could create a "base" collection containing all of the routes, and simulate different API behaviors creating collections from the "base" one. So, if you had to add another url to the API, for example, you could add it to the "base" collection and all of the other collections would inherit it.
As you will see in the next section, the order in which routes are added to a collection may be important. So, it is important to know that, when extending collections and redefining the variant of one route, the route will keep the same order in which it was originally defined in the first collection. For example:
module.exports = [
{
id: "base",
routes: ["get-users:all", "get-user:id-1", "delete-user:success", "create-user:success"]
},
{
id: "user-2",
from: "base",
routes: ["get-user:id-2"] // "get-user" route is still in the second place of the collection
}
];
The order matters
Note that the order in which route variants are added to the array may be important. The first route in the array matching the route method and route url will handle the request.
If many routes match the url and method:
- The first route variant sending a response will produce the rest to be ignored.
- If the first variant matching the url and method is of type
middleware
and it calls to thenext
parameter, then the next route matching the url and method will be executed.
So, you can use the order of the routes in a collection to apply middlewares in the order that you want.
When creating collections extending from another one, the new route variant will replace the old one in the same position that it was originally defined. Read extending collections above for further info.
Good practices
Base collection
It is recommended to create a "base" collection containing all of the routes, and simulate different API behaviors creating collections from the "base" one. So, if you had to add another url to the API, for example, you could add it to the "base" collection and all of the other collections would inherit it.
The name of your "base" collection could be any other one, such as "main", "default", "all-routes", etc.
Limit the amount of collections
Sometimes you'll need to change the response of only one specific route. Instead of creating a new collection for that, you can set custom route variants and the current collection will be modified in memory.