Recently I built a microservice and needed to build out some integration tests. In the process of building the tests, I realized that my microservice was reliant upon some external APIs that needed to be up for my tests to pass. Subsequently, I realized I could use a Mock Server for the external API calls, and as such keep my testing siloed within my application.
This post is going to share some of the things I learned, and how I used a Mock Server for Integration Tests.
My Microservice
So before going into testing, I wanted to explain what my microservice is and what it tests.
Living near the DC Metro area, I commonly take the metro trains into work everday. Their schedules can be somewhat intermittent, and I was looking for a way to be able to plan my day.
The Washington Metropolitan Area Transit Authority (WMATA) has a set of APIs that are publicly accessible. You can use their endpoints for things like arrival times, station information, and the like. I wanted get specific information from those APIs, and thought it would be fun to write an orchestration service that calls the APIs and returns the data in a format for a frontend application to consume.
So I created ms-metro.
ms-metro is open source and can be viewed here.
The API has 5 main endpoints that include the following:
/station-list
/stationInformation
/station-hours
/arrival-times
/station-route
My Test Strategy
So when I wrote out my Integration Tests, I used Mocha and Chai. I decided to use chai-http as it had a nice way of handling direct requests to my running application.
This was all great, but I still faced the challenge of the WMATA API calls that my app was doing.
So I decided that I would use environment variables to determine when the API calls were made during Integration Tests. Then when that happens, I would call a Mock Server in lieu of the actual HTTP call.
This looks like the following:
const stationList = async LineCode => {
if (process.env.MOCK_SERVER) {
return mockServer('http://localhost:3000/station-list');
}
const options = {
uri: 'https://api.wmata.com/Rail.svc/json/jStations',
qs: {
LineCode: LineCode
},
headers: {
api_key: process.env.WMATA_API_SECRET_KEY
},
json: true
};
const response = await rp(options);
const { Stations: stations } = response;
return stations;
};
If you notice here I am checking for the environment variable MOCK_SERVER
.
The method mockServer
then looks like the following:
const mockServer = async jsonLocation => {
const options = {
uri: jsonLocation,
json: true
};
return rp(options);
};
I also, make use of additional environment variables in my Integration Testing to specify to the json-server
specific payloads for specific tests. An example of this looks like the following:
const nextTrains = stationCode => {
if (process.env.MOCK_SERVER) {
// here an environment variable is used to determine which value the mock server retrieves
if (process.env.ARRIVAL_TIME === 'C02') {
return mockServer('http://localhost:3000/C02-times');
} else {
return mockServer('http://localhost:3000/next-trains');
}
}
const options = {
uri:
'https://api.wmata.com/StationPrediction.svc/json/GetPrediction/' +
stationCode,
headers: {
api_key: process.env.WMATA_API_SECRET_KEY
},
json: true
};
return rp(options);
};
Wait, so that's all cool but what is this thing running on http://localhost:3000/station-list
? That's my Mock Server 😁😁.
The Actual Mock Server
While my tests are running, I run an instance of json-server. To setup json-server
is fairly simple, you just have to install the npm package and then have a JSON file that is stored somewhere for it to read. the json-server
is super easy to work with because you just give it a path and it will parse the JSON file you specified in config and pull out the payload. I encourage you to checkout their npm page for more info.
Additionally, I must note that the json-server
documentation encourages you to install the npm pacakge globally. This was not ideal since I wanted to run it in CI etc. So I actually installed it within my project and the reference the package directly in an npm script. This looks like the following:
./node_modules/.bin/json-server --watch ./mock-server/response.json,
Also, note that when I run my Integration Tests I set the environment variable with an npm script as follows:
MOCK_SERVER=true mocha --exit test/integration.js
Setting the environment variable in the npm script is nice because I don't have to remember to set it if I'm on a different computer etc.
So now this is all great, but wait, I need to run these two processes side by side. How do I do this? That's the next thing I'm going to discuss.
Running Together
So up to this point, you've seen how I wrote my tests and how I stood up a Mock Server. However, I need these things to run together when I test both locally and in CI. The great part is, there are many utilities out there that will help you with this. I chose concurrently and start-server-and-test.
I use concurrently for my local testing, and then I use start-server-and-test for my CI testing.
To run both of these is very intuitive. You just pass the processes you want to run along with the address to search for.
local testing:
concurrently "npm run json-server" "npm run integration-tests"
CI testing:
start-server-and-test "npm run json-server" http://localhost:3000 "npm run integration-tests"
(note in the above I have npm scripts for the json-server and actually running the integtarion tests. For more please consult my project's repo.)
Wrapping Up
So as you've seen in this post, a Mock server is super nice because it contains your Integration Testing within your application. You can even run these tests offline or in a closed network if you needed. I encourage you to check out json-server
as well as my project for more.
Feel free to leave comments or connect with me on Twitter at @AndrewEvans0102.
Top comments (1)
Thank you so much.