Skip to content

QueryMock - A containerized mock server that can mock query endpoints!

License

Notifications You must be signed in to change notification settings

bradleybarrett/querymock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Table of Contents

QueryMock Overview

A fast, containerized mock server that can mock query endpoints!

Run tests in seconds: start the mock servers once and reconfigure on the fly for subsequent tests.

Configured with json files and managed by simple, language-specific clients. Clients provided at this time: Bash, Java, Python.

Define an endpoint mapping and data set, then query that data using mvel expressions templated with request info.

Example endpoints mocked by a query:

  • Find a record by id.
  • Find all records with a particular status.
  • Find all records with a status in a certain range of values.

Useful for mocking query endpoints called multiple times in a single test. In this case, QueryMock lets you create one mock endpoint with dynamic data instead of multiple mock endpoints with static data. This reduces mock complexity and makes tests easier to write and maintain.

Language-specific clients execute docker commands to manage mock servers. Each mock server is run as a container configured by json files.

QueryMock is implemented as a customized instance of WireMock with extensions for response templating and query support. This tool provides all the existing features of WireMock along with templates and queries.

For each mock endpoint, specify the...

  1. Data to query
    • json payload
  2. Query expression to evaluate
    • mvel expression templated with request data
  3. Response payload to hold the query result
    • json payload templated with request data

Note: At this time, the data to query cannot be templated with values from the http request. The query result is substituted into the templated response payload as-is.

Example 1: Get location by id

Endpoint to mock:

  • GET /location/{locationId}
  • Query result is populated in the "Data" field of the reponse.

Test directory with config files:

/mappings
    getLocationById.json     <=== Endpoint Mapping
/__files
    queryLocationById.json   <=== Query Details
/querydata
    location.json            <=== Query Data

/mappings/getLocationById.json

{
  "request": {
    "method": "GET",
    "urlPattern": "/location/([A-Za-z0-9-_]+)"
  },
  "response": {
    "status": 200,
    "bodyFileName": "queryLocationById.json",
    "transformers": ["body-transformer", "query-transformer"],
    "transformerParameters": {
      "urlRegex": "/location/(?<id>.*?)"
    }
  }
}

/__files/queryLocationById.json

{
  "query": "locationId == '$(id)'",
  "data": "location.json",
  "findOne": true,
  "bodyTemplate": {
    "Data": "{{ result }}"
  }
}

/querydata/location.json

[
  {
    "locationId": "locationId_1",
    "status": "open"
  },
  {
    "locationId": "locationId_2",
    "status": "open"
  },
  {
    "locationId": "locationId_3",
    "status": "closed"
  }
]

Input Request: GET /location/locationId_1

Body of response returned by the mock server:

{
  "Data": {
    "locationId": "locationId_1",
    "status": "open"
  }
}

Input Request: GET /location/locationId_3

Body of response returned by the mock server:

{
  "Data": {
    "locationId": "locationId_3",
    "status": "closed"
  }
}

Example 2: Get locations by status

Endpoint to mock:

  • GET /location?status=someValue
  • Response is the query result as a list.

Test directory with config files:

/mappings
    getLocationByStatus.json     <=== Endpoint Mapping
/__files
    queryLocationByStatus.json   <=== Query Details
/querydata
    location.json                <=== Query Data

/mappings/getLocationByStatus.json

{
  "request": {
    "method": "GET",
    "urlPattern": "/location\\?status=([A-Za-z0-9-_]+)"
  },
  "response": {
    "status": 200,
    "bodyFileName": "queryLocationByStatus.json",
    "transformers": ["body-transformer", "query-transformer"]
  }
}

/__files/queryLocationByStatus.json

{
  "query": "status == '$(status)'",
  "data": "location.json",
  "findOne": false
}

/querydata/location.json

[
  {
    "locationId": "locationId_1",
    "status": "open"
  },
  {
    "locationId": "locationId_2",
    "status": "open"
  },
  {
    "locationId": "locationId_3",
    "status": "closed"
  }
]

Responses from mock server:

Input request: GET /location?status=open

Response body:

[
  {
    "locationId": "locationId_1",
    "status": "open"
  },
  {
    "locationId": "locationId_2",
    "status": "open"
  }
]

Input request: GET /location?status=closed

Response body:

[
  {
    "locationId": "locationId_3",
    "status": "closed"
  }
]

Write a Test with the Client (pseudo code example)

Test resource directory with config json files for each test:

/test
    /resources
        /test1
            /wiremock
                /location
                    /mappings
                        getLocationById.json
                    /__files
                        queryLocationById.json
                    /querydata
                        location.json
                /item
                    /mappings
                        getItemById.json
                    /__files
                        queryItemById.json
                    /querydata
                        item.json
        /test2
            /wiremock
                /location
                    /mappings
                        getLocationById.json
                    /__files
                        queryLocationById.json
                    /querydata
                        location.json

Test Pseudo Code (utilizes mock reconfiguration for faster execution times)

String baseDirectory = "/test/resources"
String locationMockName = "location-mock"
String itemMockName = "item-mock"

// utility method to ensure existing location-mock instances are reconfigured whenever possible
startLocationMock(String wiremockSubDirectory)
{
    querymock.startMock(locationMockName, 8081, 8091, baseDirectory, wiremockSubDirectory)
}

// utility method to ensure existing item-mock instances are reconfigured whenever possible
startItemMock(String wiremockSubDirectory)
{
    querymock.startMock(itemMockName, 8082, 8092, baseDirectory, wiremockSubDirectory)
}

test1()
{
    startLocationMock("/test1/wiremock/location")
    startItemMock("/test1/wiremock/item")
    querymock.waitForMocks(locationMockName, itemMockName)

    // execute test code here...
}

test2()
{
    startLocationMock("/test2/wiremock/location") // will reconfigure the existing location-mock
    querymock.waitForMocks(locationMockName)

    // execute test code here...
}

runAllTests()
{
    // execute tests
    test1()
    test2()
    
    // clean-up mocks
    querymock.stopMocks(locationMockName, itemMockName)
}

Implementation Details

The wiremock instance is run as a spring application in a docker container. Configuration files for the wiremock stubs (mappings, files, and querydata) are provided to the container as a bindmount directory. The container with bindmount config allows the wiremock instance to be configured and run the same way for any programming language. Simple language-specific clients can be created to execute docker commands which start and stop the mock instance.

The BodyTransformer wiremock extension is used for templating the query and response with information from the request. Refer to the BodyTransformer documentation for additional details on the replacement syntax and supported template features: https://github.com/opentable/wiremock-body-transformer.

At start-up, data from the /querydata directory is loaded into memory. Data is queried for a mock endpoint using the QueryDetails specified in the body file of the endpoint mapping. Query data can be reused for multiple endpoints.

Build the Docker Image

# navigate to the app directory and build the docker image
cd app/querymock-app && ./docker-build.sh && cd ../..

# check that the image has been created (will print some info if present)
docker images | grep querymock