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 |
node -v
npm install -g typescript npm install -g ts-node
Initially we will configure the API and UI to be integrated with the Marvel server.
git clone https://github.com/AHaydar/mocks-demo.git
cd mocks-demo rm -rf mockserver
cd api npm i npm start
cd ui npm i npm start
{ characters { name, description } }
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
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.
mkdir mockserver cd mockserver touch docker-compose.yml
mockServer: image: jamesdbloom/mockserver:latest ports: - 1080:1080 environment: - LOG_LEVEL=WARN
docker-compose up
We will need to create a very basic node project that will launch the mockserver.
mkdir mockserver cd mockserver npm init -y
npm install mockserver-node
touch index.js
var mockserver = require('mockserver-node'); mockserver.start_mockserver({ serverPort: 1080, trace: true });
node index.js
Lets check the mockserver is up and running, and that we can setup & retrieve a dummy mock response.
curl --location --request PUT 'http://localhost:1080/mockserver/reset' --data-raw ''
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 :-)" } }'
curl --location --request PUT 'http://localhost:1080/mockserver/retrieve?type=ACTIVE_EXPECTATIONS' --data-raw ''
We can now direct the API (backend) to connect to the mockserver instead of the Marvel server
In the API terminal
ctrl+c
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
const isMockEnabled = process.env.mock === 'true'; export const baseURL = isMockEnabled? 'http://localhost:1080':'https://gateway.marvel.com:443';
... "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?)
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.
We can use the Marvel swagger documentation to generate the models as follows
mkdir mocks cd mocks npm init -y npm i mockserver-client npm i @openapitools/openapi-generator-cli
{ "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" } }
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.
Expectations are used by the mockserver to configure the responses sent to matching requests.
touch constants.ts runner.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)
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.
ts-node runner.ts
{ httpRequest: { method, path, queryStringParameters, headers, body }, httpResponse: { statusCode: responseCode, headers: responseHeaders, body: JSON.stringify(responseBody) }, times: { remainingTimes } }
const main = () => { ... };
const main = () => { const httpRequest = { method: 'GET', path: '/v1/public/characters' }; };
import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper'; const main = () => { ... };
... const charactersResponseBody: CharacterDataWrapper = { data: { results: characters } }; ...
import { characters } from './constants'; import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper'; const main = () => { ...
import { mockServerClient } from 'mockserver-client'; import { characters } from './constants'; import { CharacterDataWrapper } from '../MarvelClients/model/characterDataWrapper'; const main = () => { ...
... const expectation = { httpRequest, httpResponse: { statusCode: 200, body: JSON.stringify(charactersResponseBody) } }; ...
... const expectation = { httpRequest, httpResponse: { statusCode: 200, body: JSON.stringify(charactersResponseBody) } }; const client = mockServerClient('localhost', '1080'); client.mockAnyResponse(expectation); };
... const client = mockServerClient('localhost', '1080'); client.mockAnyResponse(expectation); };
... const client = mockServerClient('localhost', '1080'); client.mockAnyResponse(expectation); }; main();