Be creative with Midway mock

Image credit: Pixabay
This blog content assumes that you have basic knowledge of Test Armada’s open source mocking server, Midway.

For successful test automation, it is essential to truly test what we intended to test and not depend on the availability of downstream web services and data. To be able to fast testing and without dealing with inconsistent live services and data, FrontEnd test automation build at Walmart depends on mocking one or more of the dependent backend services using Midway. So you may imagine the profound impact if mocking doesn’t work the way we expected.


It starts with a problem

To load an item on the browser, the application makes web services requests to downstream services to retrieve the item data.

These services APIs used to have different URL paths and we mock them with Midway, the mocking framework which support mocking APIs with different paths. This has worked fine until a redesign that changed these APIs to POST requests but with the same URL path. This creates a problem as Midway doesn’t support mocking different mock routes with the same URL path.


How it used to work

Here’s some of the requests that we mock with Midway:

  1. Get item: /item/{id}
  2. Get shipping info: /shipping/{id}
  3. Get pickup info: /pickup/{id}

Since they have different URL paths; the logical way to mock the different requests in Midway is to define different routes. For instance,

  1. Get item route:
module.exports = {
register: function (mock) {
    mock.route({
id: "getItem",
label: "getItem",
path: "/item/{id}",
method: "GET",
handler: function (req, reply) {
mock.util.respondWithFile(this, reply);
}
})
    .variant({
id: "giftCard",
label: "giftCard",
handler: function(req, reply) {
mock.util.respondWithFile(this, reply);
}
})
.variant({
id: "eGiftCard",
label: "eGiftCard",
handler: function(req, reply) {
mock.util.respondWithFile(this, reply);
}
})
}
};

2. Get shipping info route:

module.exports = {
register: function (mock) {
    mock.route({
id: "getShipping",
label: "getShipping",
path: "/fulfillment/{id}/shipping",
method: "GET",
handler: function (req, reply) {
mock.util.respondWithFile(this, reply);
}
})
    .variant({
id: "freightShipping",
label: "freightShipping",
handler: function(req, reply) {
mock.util.respondWithFile(this, reply);
}
})
.variant({
id: "standardShipping",
label: "standardShipping",
handler: function(req, reply) {
mock.util.respondWithFile(this, reply);
}
})
}
};

3. Get pickup info route:

module.exports = {
register: function (mock) {
    mock.route({
id: "getPickup",
label: "getPickup",
path: "/fulfillment/{id}/pickup",
method: "GET",
handler: function (req, reply) {
mock.util.respondWithFile(this, reply);
}
})
    .variant({
id: "pickupOnly",
label: "pickupOnly",
handler: function(req, reply) {
mock.util.respondWithFile(this, reply);
}
})
}
};

This works great until the different APIs that were mocked in different mocking routes now have the same URL path. In spite of the different query strings pass to the different POST requests; Midway can’t differentiate the requests with the same URL path. Ugh…

Although the downstream services continue to support the GET requests, the application has already migrated to the new POST requests in production. The test can continue to use the GET requests but this created a problem that the test is not testing the same APIs that are used from the application.

The immediate approach was to request the Midway team to support different routes for the same URL path.

Turns out this new feature request won’t be available soon and we ended up running the automation tests with the outdated GET requests for a while. Sad and we need to find another solution fast…


The solution

To workaround this, a simple solution is to route different POST requests to return different mock data in the same Midway route.

With the same three requests, the APIs now have the same url path, /fetch:

  1. Get item
  2. Get shipping info
  3. Get pickup info

When any one of the three requests comes into the mock server, the mock server sees it as the same request because the URL path is all the same. The mock server can’t differentiate with different query strings either so it uses the same mocking route to handle 1) Get item, 2) Get shipping info, and 3) Get pickup info.

To handle different requests in the same route, three different functions were added to load the mock data from different mock data file locations:

// 1. Get item
const _getItemVariant = (mock, variantId) => {
return {
id: `${variantId}`,
label: `${variantId}`,
method: "POST",
handler(req, reply) {
       var mockedPath = "test/automation/mocked-data/fetch/item/POST/";
mock.util.respondWithFile(this, reply, { filePath: mockedPath + variantId + ".json" });
}
};
};
// 2. Get shipping info
const _getShippingVariant = (mock, variantId) => {
return {
id: `${variantId}`,
label: `${variantId}`,
method: "POST",
handler(req, reply) {
var mockedPath = "test/automation/mocked-data/fetch/shipping/POST/";
mock.util.respondWithFile(this, reply, { filePath: mockedPath + variantId + ".json" });
}
};
};

// 3. Get pickup info
const _getPickupVariant = (mock, variantId) => {
return {
id: `${variantId}`,
label: `${variantId}`,
method: "POST",
handler(req, reply) {
var mockedPath = "test/automation/mocked-data/fetch/pickup/POST/";
mock.util.respondWithFile(this, reply, { filePath: mockedPath + variantId + ".json" });
}
};
};

Then depending on the selected mock variant set in the test, the mocking route function calls one of the above three functions to return the correct mock data.

For example:

module.exports = {
register(mock) {
mock.route({
id: "getItem",
label: "getItem",
path: "/fetch",
method: "POST",
handler(req, reply) {
var mockedPath = "test/automation/mocked-data/fetch/item/POST/";
mock.util.respondWithFile(this, reply, { filePath: mockedPath + "default.json" });
}
})

// Get item call
     .variant(_getItemVariant(mock, “giftCard”))
     .variant(_getItemVariant(mock, “eGiftCard”))

     // Get shipping info call
     .variant(_getShippingVariant(mock, “shipOneDay”))
     .variant(_getShippingVariant(mock, “shipTwoDays”))

     // Get pickup info call
     .variant(_getPickupVariant(mock, “pickupInPreferredStore”))
     .variant(_getPickupVariant(mock, “pickupInNearbyStore”))
   }
};

As you can see, the get item request calls _getItemVariant() to retrieve the mock data.

The get shipping info request calls _getShippingVariant() to retrieve the mock data.

And the get pick up info request calls _getPickupVariant() to retrieve the mock data.

We are now handling different requests with the same URL path within the same mocking route. This solved our problem and we are able to test with the right POST request APIs.

Image credit: Pixabay

Sometime to unblock a problem that out of box solution can’t solve, we just have to be creative.