Before reading

Hope you can have the following foundation, easy to read:

  • ECMAScript 6 (ES6)

Why Mock

Today’s business systems rarely exist in isolation, especially in a large company, where departments work closely together and we more or less need to use interface services provided by sibling teams or other companies. In this case, it will cause great trouble to our joint investigation and test. If the pace of each brother department is completely consistent, then the problem will be much less, but the ideal is full, the reality is very skinny, it is impossible to achieve consistent pace.

To do this, we need to use tools that help us decouple business units as much as possible. This is Mock

What is the Mock

Mock, when translated by itself, means “fake or false”, so in software development we can also understand mock as “fake data” or “a stand-in for real data”.

The benefits of a Mock

  • Teams can work better in parallel

When using the mock, can no longer need to wait for each other between the teams progress, only need to agree on each other between the data specification (document), you can use a mock build a usable interface, for development and debugging, and then as soon as possible, promote development of progress at the same time, will also find the timing of the defects in advance greatly.

  • Enable the test-driven Development (TDD) mode

Unit tests are the cornerstone of TDD implementations, and TDD often runs into situations where collaborative modules are not yet developed, but with mocks, none of this is a problem. Once the interface is defined, the tester can create a Mock to add the interface to the automated test environment and create the test ahead of time.

  • Test coverage

Such as an interface in a variety of different conditions to return different values, before our approach is repetition in this state and then to request interface, which is very unscientific, and the retrieval method very possible because of the timing of the operation or improper operation failure, even before the pollution data in the database. If we use mocks, we don’t have to worry about these problems at all.

  • Convenient demo

By using Mock to simulate the data interface, we can demonstrate the product without the need for server-side development without developing only the UI.

  • Isolation system

To avoid database contamination, Mock interfaces can be used to keep the database clean.

Now that you’re ready to Mock, let’s talk about some ways to implement mocks.

To realize the Mock

“Stubborn Bronze”

Well, let’s start with the most stubborn of all “bronzes.” Without mocks, how can we develop without real interfaces?

In my memory, when faced with such a situation, MY first practice is to write the data in the business, such as:

// api
import api from '.. /api/index';

function getApiMessage() {
    return new Promise((resolve) => {
        resolve({
            message: 'Request successful'}); / /})return api.getApiMessage();
}
Copy the code

I comment out the real request, return a resolve promise of false data in place of the real request, and then when I call this method I return a resolve promise of my own defined false data instead of the promise obtained from the unfinished interface. It looks good, at least I can continue to develop without an interface. Although it can be a bit painful to write your own data when it comes to complex lists.

But is it really good that fake data and business are so coupled? If you forget to remove the fake data when the real interface is complete because the business can “work correctly”, you end up using data you made up rather than real, that’s a serious problem. So the next thing we need to think about is how to minimize the amount of fake data written into the business code. To that end, let’s officially advance to the glory gold section of the mock.

“Glory Gold”

In the glory gold section of mock, we have a very handy tool: mockJs, which allows us to generate complex interface data from templates and rules without having to write it ourselves, for example:

// api
import api from '.. /api/index';
import Mock from 'mockjs';

function getApiMessage() {
    return new Promise((resolve) => {
        resolve(Mock.mock({
            list|1-20: ['the mock data']}); / /})returnapi.getApiMessage(); } / * * * through the Mock in Mock method and list | 1-20: ['the mock data'] template * we will generate a 1-20 length, each value is'the mock data'An array of * /Copy the code

But doing so will only make it easier for us to “fake”, and will not actually remove “fake” from our business code. To achieve this goal, we might as well analyze our requirements first:

  • Simulated data is completely separated from business code
  • With some configuration, only a portion of the data is mock out, and most of the data is retrieved from the request

First of all, if we want to completely separate the mock data from the business code, we have to find a way to mock data at request time rather than request the actual interface. This is called “request interception”, and request interception can also be implemented in two ways:

  • Modify requests are linked to mock-Server, where mock data and routing are configured
// API /index.js // mock method import Request from by adding the getDataUseMock method'.. /request';

function getDataUseMock(data) {
    request({
        mock: true
    });
}
// request/index.js
const mockServer = 'http://127.0.0.1:8081';

function request(opt) {
    if (opt.mock) {
        const apiName = opt.api;
        opt.url = `${mockServer}/${apiName}`; }... }Copy the code
  • Directly when detecting the use of a mock, fetch the data corresponding to the key value from the mock data file
// API /index.js // mock method import Request from by adding the getDataUseMock method'.. /request';

function getDataUseMock(data) {
    request({
        mock: true
    });
}

// request/index.js
import mockData from 'mock/db.js';

function request(opt) {
    if (opt.mock) {
        const apiName = opt.api;
        return new Promise((resolve) => {
            resolve(mockData.apiName)
        })
    }
    ...
}

//mock/db.js
export default {
    '/api/test': {
        msg: 'Request successful'}}Copy the code

At first glance like a second way seems to be more simple, it is, but given that if I were directly read data from the file directly, so the behavior of the business will also change, the place where the request for the hair and did not send the request, so I chose to build a local service, by controlling the routing return different mock data to deal with, And by adding an extra mock parameter to the request to inform the business which interface should be intercepted by the self-built mock-Server, the impact on the original business is minimized.

Before mock-Server development, we need to understand what our mock-Server should be able to do:

  • What you change is what you get, hot update capability, and no need to restart the Mock service or the front-end build service every time you add/modify the mock interface
  • Mock data can be generated by tools without having to write it yourself
  • Can simulate POST and GET requests

Since mock mock data is maintained locally, all we need is a server that has no interface and can respond to requests, so I choose JSON-server

Before building the Server, we need to figure out what data we need to emulate and what to maintain with mockJS

// db.js
var Mock = require('mockjs'); Module.exports = {getComment: mock. mock({getComment: mock. mock)"error": 0."message": "success"."result|40": [{
      "author": "@name"."comment": "@cparagraph"."date": "@datetime"}]})};Copy the code

Second, we need to know which access routes we jump to:

Module.exports = {// routes. Exports = {// routes. Exports = {// routes."/comment/get": "/getComment"
}
Copy the code

Then we can write our main code for starting the server:

// server.js
const jsonServer = require('json-server')
const db = require('./db.js')
const routes = require('./routes.js') const port = 3000; Router = jsonServer.router(db) const middlewares = Jsonserver.defaults () // Rewrites routes from the route list const rewriter = jsonServer.rewriter(routes) server.use(middlewares) // Converts the POST request to the route Server. use((request, res, next) => {request.method => {request'GET'; next(); }) server.use(router) server.listen(port, () => {console.log(port, () => {console.log())'open mock server at localhost:' + port)
})
Copy the code

As a result, it is possible to launch a mock server using Node server.js, but the server does not update in real time when I modify route.js or db.js. In other words, I need to restart my server every time to update it. There is one more small operation we need to do, such as using Nodemon to monitor our mock-Server.

// Put all mock related files: db.js route.js server.js into the mock folder // then execute: $nodemon --watch mock mock/server.js //Copy the code

After that, we can mock the specified API in our own business code using methods like getDataUseMock that we defined earlier.

While we have achieved a complete separation of mock data from business code by doing so, it is inevitable that special methods are used in business code to declare that I need to mock an interface, and that we also face the problem of removing these methods and replacing them with formally requested methods when mocks are no longer needed. The mock data is still stored in the same Git directory as the business code, and only the developer has permission to modify and add to it, which doesn’t work as well as it should.

To this end, I asked the department Leader and “a lot” of developers what we wanted our mock to look like:

  • It can be used with as little modification as possible to the business codemock
  • The modified business code does not affect normal business processes
  • mock-serverIt should be for everyone, not just the front-end developers
  • Ability to visually modify and addmockThe interface andmockdata
  • Can support multiple projects at the same time

With the help of these basic principles, our mock has finally advanced to the “Eternal Diamond” level.

Eternal Diamonds

With the help of the diamond section, I found mock-Server’s “upper sharpener” : RAP2 from the Open source THX tool library of Alibaba’s front end team, which contains all the advantages I need for mock. After deploying RAP2 to our local server following the online tutorial, we only need to configure the hosts file locally to access our own RAP2. After that, all we need to do is the processing in the business code:

  • It can be used with as little modification as possible to the business codemock
  • The modified business code does not affect normal business processes

In order to change the code as little as possible without affecting the normal business process, we need to add a special development mode in which the code we change will only take effect, or exist.

We can name our new development pattern mock development pattern. To distinguish this development pattern, we use environment variables in NodeJS.

"scripts": {
    "dev:mock": "cross-env MOCK=true npm run dev"
}
Copy the code

MOCK After we declare the environment variable with cross-env, we can use process.env.mock to get the value of our declared environment variable.MOCK interception occurs when the added MOCK variable exists and is true.

But it’s not enough to just declare this, we also need to notify the business code which interfaces need to be mocked. So, we also need a list that exists in mock mode to tell us which interfaces should be mock.

// config.js
if (process.env.MOCK) {
    config.mockList = [
        '/api/test'.'/api/needMock']}else {
    config.mockList = [];
}
Copy the code

Even better, you can use conditional compilation to determine whether or not you want to insert config.mockList into your code.

Next, you just need to compare the mockList of config with the API of your current request in the request method you encapsulate to see if it mocks.

import config from '.. /config/config';

const mockServer = 'http://rap2.xxx.com'

function request(opt) {
    const apiName = opt.api;
    if (config.mockList && config.mockList.includes(apiName)) {
        opt.url = `${mockServer}/${apiName}`; }... }Copy the code

At last, our mock has reached its final form, and we can now develop and test as much as we want as long as the interface document (even RAP2’s mock interface can be used as an interface document)

The use of RAP2

Start with the team

The team is the parent of the repository, and a team can have multiple mock repositories, but not just the team, but individuals. The purpose of using a team is simply to keep the team’s repository hidden from anyone outside the team and to keep the team private (although you can also choose to make the team public).

warehouse

The warehouse is the superior unit of the interface, which can be owned by individuals or teams. Each warehouse can assign developers, and the designated staff can modify or add the interface of the warehouse, while the unassigned staff can only view the interface. Each warehouse has a specific warehouse domain name prefix. ${warehouse prefix domain name}${interface configuration domain name}, and each warehouse provides an interface to obtain the current warehouse data.

interface

Let’s first look at the composition of the interface configuration page:

  • Creating an Interface (Interface List)
  • Interface module
  • Interface details (request and response parameters)

In the interface detail, the route to the mock interface is created when the interface is created, and then an interface is automatically generated. The request address is ${warehouse domain}${interface route}.

The most important thing is to generate rules and default values. The rules and templates can refer to the syntax specification in the mockJs document. The generated rules follow the Data Template Definition (DTD). Default values conform to the Data Placeholder Definition (DPD).

Refer to the content

  • Mock tests, where to go
  • Hand-build the mock-Server in the front-end and back-end separation project
  • Teach you to use Docker deployment Taobao RAP2 service