User Tools

Site Tools


wiki:workshops:api-mocks

API Mocks and Testability

Who Ali Haydar
When Tue June 2nd through to Thu June 4th, 2020
WEBEX
recordings
(pwd)
Session 1 (eN6egYpn)
Session 2 (sEp7W2s9)
Session 3 - not recorded (sorry we forgot)
Event Links Post on medium
MoT Akl (Webex video)
Slack #api_mocking_workshop
meetup event
References mockserver-node
mockserver-client

Required knowledge

  • Terminal usage for your OS
  • Basic javascript
  • Basic Node.js
  • Basic TypeScript

Pre-requisites

  • Install Node.js & npm (JavaScript runtime)
    • Go to (https://nodejs.org/en/) to download, and then install the LTS (Recommended for most users)
    • npm is installed automatically with NodeJS, when you install node you get npm with it
    • verify the installation with
      node -v
  • Install ts-node (TypeScript for Node.js):
      npm install -g typescript 
      npm install -g ts-node
  • Install Docker (https://docs.docker.com/get-docker/). Docker is used to run the mock server, but the mockserver can also be installed as a node application too.
    • [optional] install a Git UI from here
  • Install VSCode or any other code editor you prefer
  • [optional] Register a Marvel account and get your API keys
    • Register here here ⇒ menu\join
    • Get your keys here and save both of them. You will need them to get the application running properly when integrated with the real third party system
    • You will not need your keys once you start the the mocks implementation
    • Note: We'll use the Marvel API's Swagger documentation to generate the types

Setup the system under test

Initially we will configure the API and UI to be integrated with the Marvel server.

  1. Clone the github repository.
    git clone https://github.com/AHaydar/mocks-demo.git
  2. Delete the mockserver folder, it will be rebuilt as part of this workshop
    cd mocks-demo
    rm -rf mockserver
  3. In a separate terminal install & start the API server. Keep this terminal open, we will refer to it as the “API terminal”
    cd api
    npm i
    npm start
  4. In another terminal install & start the UI server. Keep this terminal open, we will refer to it as the “UI terminal”
    cd ui
    npm i
    npm start
  5. If you have created a Marvel account, you can now

Setup the mockserver

The mockserver will substitute the Marvel API server. That is, our API (running in the API terminal) will connect to the mockserver instead of to the Marvel server. Our UI (running in the UI terminal) will continue connect to our API, but when we're done, we'll see the data from the mocks.

We can setup the mockserver using either

  • a docker compose image
    • This is preferable, but docker's minimum system requirements means that you may need to do the following instead
  • installing the mockserver as a node application

For both options, the mockserver will be a separate running instance of mockserver-node, and has an API that we will use to create individual mock responses prior to doing any testing of the web site.

In our test code we will be using the mockserver-client node module to help us create & send 'expectations' to the mockserver, which configures the response on the mockserver.

Mockserver - Docker setup

  1. In a new terminal create a file docker-compose.yml inside Mocks-Demo\mockserver folder.
    Keep this terminal open, we will refer to it as the “mockserver terminal”
    mkdir mockserver
    cd mockserver
    touch docker-compose.yml
  2. Edit the file
    docker-compose.yml
    mockServer:
      image: jamesdbloom/mockserver:latest
      ports:
      - 1080:1080
      environment:
      - LOG_LEVEL=WARN
  3. Start the Docker container
    docker-compose up

Mockserver - Node setup

We will need to create a very basic node project that will launch the mockserver.

  1. In a new terminal, create a new folder, and initialise it as a node project
    Keep this terminal open, we will refer to it as the “mockserver terminal”
    mkdir mockserver
    cd mockserver
    npm init -y
  2. install mockserver-node from npm.
     npm install mockserver-node
  3. Create index.js inside Mocks-Demo\mockserver\ folder
    touch index.js
  4. Edit the file
    index.js
    var mockserver = require('mockserver-node');
    mockserver.start_mockserver({
        serverPort: 1080,
        trace: true
    });
  5. Run the node project to start the mockserver
    node index.js

Verify Mockserver

Lets check the mockserver is up and running, and that we can setup & retrieve a dummy mock response.

  1. In a new terminal, send the reset command
    curl --location --request PUT 'http://localhost:1080/mockserver/reset' --data-raw ''
  2. Create a dummy expectation
    curl --location --request PUT 'http://localhost:1080/mockserver/expectation' \
    --header 'Content-Type: text/plain' \
    --data-raw '{
      "httpRequest" : {
        "method" : "GET",
        "path" : "/some/path"
      },
      "httpResponse" : {
        "body" : "success! The mockserver is running :-)"
      }
    }'
  3. Retrieve loaded expectations
    curl --location --request PUT 'http://localhost:1080/mockserver/retrieve?type=ACTIVE_EXPECTATIONS' --data-raw '' 
  4. Open the dummy response http://localhost:1080/some/path in your browser

Connect the AP to mockserver

We can now direct the API (backend) to connect to the mockserver instead of the Marvel server

In the API terminal

  1. Kill the API server process with
    ctrl+c
  2. Restart it with
    npm run start:mock

    This starts the API with an environment variable, so that it connects to the mockserver on port 1080.
    The code for this can be found here

    api/config/environmentConstants.ts
    const isMockEnabled = process.env.mock === 'true';
    export const baseURL = isMockEnabled? 'http://localhost:1080':'https://gateway.marvel.com:443';
  3. Note: there is a problem with running the above on windows because the highted line fails to set the mock environment variable
    api/package.json
      ...
      "main": "index.js",
      "scripts": {
        "start:mock": "mock=true ts-node index.ts", 
        "start": "ts-node index.ts"
      },
      "keywords": [],
      ...

    It is possible to use cross-env from npm to fix this. (TBD: Could someone document the details please?)

Creating the mockserver expectations

In this section we'll write the code that creates and sends the expectations to the mockserver. Once the expectations are sent and loaded the mockserver will be able to respond to matching requests. This section uses the mockserver-client node module to talk with the mockserver, and uses the Marvel API's swagger documentation to generate the object types. The character type will then be used to generate the expectation for fetching the list of characters.

Generate Marvel API Object Models

We can use the Marvel swagger documentation to generate the models as follows

  1. In a new terminal, create a new node project 'mocks', and install the dependencies:
    mkdir mocks
    cd mocks
    npm init -y
    npm i mockserver-client
    npm i @openapitools/openapi-generator-cli
    • mockserver-client ⇒ is used to interact with the mockserver
    • @openapitools/openapi-generator-cli ⇒ generates the models from the swagger documentation

  2. Add the highlighted line to package.json
    /mocks-demo/mocks/package.json
    {
      "name": "mocks",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "build:clients-openapi": "rm -rf ../MarvelClients && openapi-generator generate --skip-validate-spec -i https://gateway.marvel.com/docs/public -g typescript-node -Dmodels -o ../MarvelClients", 
        "test": "echo \"Error: no test specified\" && exit 1"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "dependencies": {
        "@openapitools/openapi-generator-cli": "^1.0.13-4.3.1",
        "mockserver-client": "^5.10.0"
      }
    }
  3. Generate the models by running the following command
    npm run build:clients-openapi

    Note: You will probably see a number of warnings, check them out, but they're probably ok.
    Then go and have a look through the files generated in MarvelClient/model, eg. characters.ts, which describes how the character looks. We'll be using these API models when building out the mocks in the following sections.

Creating the Expectations

Expectations are used by the mockserver to configure the responses sent to matching requests.

  1. Create empty files
    touch constants.ts runner.ts
  2. Edit them
    constants.ts
    export const characters = [
        {
          name: 'Aaron Stack',
          description: '',
        },
        {
          name: 'Abomination (Emil Blonsky)',
          description:
            'Formerly known as Emil Blonsky, a spy of Soviet Yugoslavian origin working for the KGB, the Abomination gained his powers after receiving a dose of gamma radiation similar to that which transformed Bruce Banner into the incredible Hulk.',
        }
    ]

    constants.ts contains the mocked data (the characters we're mocking)

    runner.ts
    import { mockServerClient } from 'mockserver-client';
    import { characters } from './constants';
    import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper';
     
    const main = () => {
    	const httpRequest = {
            method: 'GET',
            path: '/v1/public/characters'
        };
    	const charactersResponseBody: CharacterDataWrapper = {
            data: {
                results: characters
            }
        };
    	const expectation = {
            httpRequest,
            httpResponse: {
                statusCode: 200,
                body: JSON.stringify(charactersResponseBody)
            }
        };
     
    	const client = mockServerClient('localhost', '1080');
            client.mockAnyResponse(expectation);
    };
     
    main();

    runner.ts configures the mockserver with an expectation.

  3. Run it to send the expectation to the mockserver instance
    ts-node runner.ts

Expectations Explained

  • constants.ts contains the mocked data
  • runner.ts configures the mockserver expectation based on the data.
  • Essentially we want to mock the api that fetches the list of characters. See the Swagger documentation
  • The expectation is an object with the following structure
    {
       httpRequest: {
          method,
          path,
          queryStringParameters,
          headers,
          body
       },
       httpResponse: {
          statusCode: responseCode,
          headers: responseHeaders,
          body: JSON.stringify(responseBody)
       },
       times: {
          remainingTimes
       }
    }
  • We are going to only mock the method and path from the request, and status code and body from the response
  • in runner.ts
    • Line 5 creates the main function
      1. const main = () => {
      2. ...
      3. };
    • Lines 6-9 creates the httpRequest object
      1. const main = () => {
      2. const httpRequest = {
      3. method: 'GET',
      4. path: '/v1/public/characters'
      5. };
      6. };
    • The Marvel API documentation shows the response body should be a `CharacterDataWrapper` a type/model that we generated earlier. It needs to be imported
      1. import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper';
      2.  
      3. const main = () => {
      4. ...
      5. };
    • Lines 10-14 creates the response body
      1. ...
      2. const charactersResponseBody: CharacterDataWrapper = {
      3. data: {
      4. results: characters
      5. }
      6. };
      7. ...
    • Where characters is the array we created in the constants.ts file. Import it at the top of the file
      1. import { characters } from './constants';
      2. import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper';
      3.  
      4. const main = () => {
      5. ...
    • Import the `mockserver-client` module so that we can use it to create the expectation
      1. import { mockServerClient } from 'mockserver-client';
      2. import { characters } from './constants';
      3. import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper';
      4.  
      5. const main = () => {
      6. ...
    • Lines 15-21 create the expectation, using the httpRequest, and charactersResponseBody
      1. ...
      2. const expectation = {
      3. httpRequest,
      4. httpResponse: {
      5. statusCode: 200,
      6. body: JSON.stringify(charactersResponseBody)
      7. }
      8. };
      9. ...
    • Line 22 creates an instance of the mockserver-client, connecting to localhost on port 1080
      1. ...
      2. const expectation = {
      3. httpRequest,
      4. httpResponse: {
      5. statusCode: 200,
      6. body: JSON.stringify(charactersResponseBody)
      7. }
      8. };
      9. const client = mockServerClient('localhost', '1080');
      10. client.mockAnyResponse(expectation);
      11. };
    • Line 23 calls the mockAnyResponse method, which sends the expectation to the mockserver
      1. ...
      2. const client = mockServerClient('localhost', '1080');
      3. client.mockAnyResponse(expectation);
      4. };
    • Lastly we call the main() function on line 26
      1. ...
      2. const client = mockServerClient('localhost', '1080');
      3. client.mockAnyResponse(expectation);
      4. };
      5.  
      6. main();

Exercises

  • Test the application, are there any problems? what might be causing this to happen?
    (hint: have a look at: `ui/src/components/Characters.js`)
    • Fix the issue (hint: the react component is expecting a character id)
  • We need a new character added to our list: create a new mock or update the existing one
    (hint: might require some online research about mockserver client)
wiki/workshops/api-mocks.txt · Last modified: 2020/06/08 01:21 by Vincent Dirks