Dictionary
- Dictionary
When Specmatic generates requests while running tests or responses while running as a stub and no examples have been provided, Specmatic will generate values for fields based on the schema in the OpenAPI specifications.
While the generated values will conform to the schema, they may not be meaningful from the point of view of your business domain. Also at times you want to have more control on the exact values that are used for certain fields.
This is where dictionary capability helps you define domain-specific values which Specmatic can use in the absence of examples. Specmatic will look up values for fields in the dictionary defined by you while generating requests or responses.
The dictionary can be supplied in either YAML or JSON format. When the dictionary file name follows the convention <spec-file-name>_dictionary.<format>, Specmatic will automatically pick it up in the context of the corresponding API specification file.
Structure
Fields are represented as nested properties that reflect the hierarchical structure of the schema.
- Each entry in the dictionary maps a schema field to either a
single valueor alist of values. - If a single value is specified, it will be used directly.
- If a list is specified, one of those values will be selected at random.
Basic Field Mapping
For example, given the Employee schema as follows:
components:
schemas:
Employee:
type: object
properties:
name:
type: string
age:
type: integer
Corresponding dictionary entries for the name and age properties would be:
-
Employee: name: John # Single-value age: # Multi-value - 20 - 30 - 40 -
{ "Employee": { "name": "John", "age": [20, 30, 40] } }
- The
namefield is supplied as asingle valueand will be used directly. - The
agefield is supplied as alist of values, from which either20,30, or40will bepseudo-randomselected.
Parameters Mapping
In the PARAMETERS section, parameters should organized based on their location: PATH, QUERY, or HEADER. Each key within these groups denotes the parameter name, which may correspond to either a singular value or an array of potential values. When multiple values are specified, one will be selected pseudo-randomly during runtime.
-
PARAMETERS: PATH: userId: 123 orderId: 456 QUERY: sort: date page: 10 HEADER: x-request-id: req-12345 -
PARAMETERS: PATH: userId: - 123 - 456 orderId: - 123 - 456 QUERY: sort: - date - time page: - 10 - 20 HEADER: x-request-id: - req-12345 - req-67890
Nested Properties
For nested structures, properties are represented as nested keys, where each level corresponds to a that level in the schema hierarchy.
For example, given the Employee schema as follows:
components:
schemas:
Employee:
type: object
required:
- name
properties:
name:
type: object
required:
- first_name
properties:
first_name:
type: string
last_name:
type: string
Corresponding dictionary entries for the first_name and last_name property would be:
-
Employee: name: first_name: John last_name: Smith -
Employee: name: first_name: - John - Jane last_name: - Smith - Doe
Handling Arrays
For properties that are arrays, the values must also be an array in the dictionary
For example, given the Employee schema as follows:
components:
schemas:
Employee:
type: object
required:
- aliases
properties:
aliases:
type: array
items:
type: string
Corresponding dictionary entries for the aliases array would be:
-
Employee: aliases: - "John" - "Jane"The entire array will be used
-
Employee: aliases: - ["John", "Jane"] # JSON style array - - "May" # YAML style array - "Jones"One of the nested arrays will be
pseudo-randomlyselected and used
Nested properties in Arrays
For example, given the Employee schema as follows:
components:
schemas:
Employee:
type: object
required:
- aliases
properties:
aliases:
type: array
items:
type: object
required:
- first_name
properties:
first_name:
Corresponding dictionary entries for the first_name property within the aliases array would be:
-
Employee: aliases: - first_name: "John" -
Employee: aliases: - first_name: "John" - first_name: "Jane"
This nesting behaves correctly because
first_nameis not defined as a top-level property in the schema. It is also not necessary to specify all properties of the object within the array, any missing properties will be populated by Specmatic.
Referencing Other Schemas
When a schema component utilizes $ref to reference another schema component, the dictionary for the referenced schema component with be defined with that schema component as the root entry (in other words directly start with the fields of the referenced schema)
For example, given the following Employee and Address schemas:
components:
schemas:
Employee:
type: object
required:
- addresses
properties:
first_name:
type: string
addresses:
$ref: '#/components/schemas/Address'
Address:
type: object
required:
- city
properties:
street:
type: string
city:
type: string
Corresponding dictionary entries for any property defined in the Address schema would be:
-
Address: city: "New York" street: "Broadway" -
Address: city: - "New York" - "London" street: - "Broadway" - "High Street"
Notice that the keys begin with Address instead than Employee, because the dictionary accesses the fields from the referenced schema. This makes for convenient dictionary definition without have to nest the values deeply.
Dictionary Generation
Commercial
Manually creating a dictionary can be quite an involved process, especially when the schema is complex. This is where, specmatic-openapi offers a convenient method to generate dictionaries from OpenAPI specifications and existing examples.
Automated dictionary generation is only available in the commercial version of Specmatic. For further details, please check the pricing page.
Let’s take a look at how we can generate a dictionary from an OpenAPI specification along with existing examples
Specification
Create an OpenApi Specification file named employees.yaml as follows:
openapi: 3.0.0
info:
title: Employees
version: '1.0'
servers: []
paths:
/employees:
post:
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/EmployeeDetails'
responses:
'200':
description: Employee Created
content:
application/json:
schema:
$ref: '#/components/schemas/Employee'
'400':
description: Bad Request
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
Employee:
type: object
required:
- id
- name
- department
properties:
id:
type: integer
employeeCode:
type: string
name:
type: string
department:
type: string
EmployeeDetails:
type: object
required:
- name
- department
properties:
name:
type: string
department:
type: string
Error:
type: object
required:
- message
properties:
message:
type: string
Examples
Given that we have an OpenAPI specification, we should add a few examples for the /employees endpoint. Create a folder named employees_examples in the same directory as your employees.yaml file
- Let’s create an example for
johnnamedemployee_john.json:
{ "http-request": { "path": "/employees", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "name": "John", "department": "IT" } }, "http-response": { "status": 200, "body": { "id": 123, "employeeCode": "EMP123", "name": "John", "department": "IT" }, "status-text": "OK", "headers": { "Content-Type": "application/json" } } } - Similarly, create an example for
janenamedemployee_jane.json:
{ "http-request": { "path": "/employees", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "name": "Jane", "department": "HR" } }, "http-response": { "status": 200, "body": { "id": 456, "employeeCode": "EMP456", "name": "Jane", "department": "HR" }, "status-text": "OK", "headers": { "Content-Type": "application/json" } } } - Now let’s create a bad-request example named
bad_request.json, where we’ve mutated thedepartmentfield:
{ "http-request": { "path": "/employees", "method": "POST", "headers": { "Content-Type": "application/json" }, "body": { "name": "James", "department": 123 } }, "http-response": { "status": 400, "body": { "message": "BAD REQUEST - Invalid field: department, value: 123 <number>, expected: <string>" }, "status-text": "Bad Request", "headers": { "Content-Type": "application/json" } } }
The _examples suffix is a naming convention that indicates to Specmatic to look for examples in that directory.
Generating the Dictionary
Create a file named employees_dictionary.yaml in the same directory as your employees.yaml file with an empty object i.e. {}, so we volume mount this file to the Docker container
After setting up, we can execute the examples dictionary command using the specmatic-openapi Docker image and provide the path to our employees.yaml file as shown below:
docker run --rm -v "$(pwd)/employees.yaml:/usr/src/app/employees.yaml" -v "$(pwd)/employees_examples:/usr/src/app/employees_examples" -v "$(pwd)/employees_dictionary.yaml:/usr/src/app/employees_dictionary.yaml" specmatic/specmatic-openapi examples dictionary --spec-file employees.yaml
Understanding the Dictionary
After executing the command, the employees_dictionary.yaml file will be updated with the following contents:
EmployeeDetails:
name:
- Jane
- John
- James
department:
- HR
- IT
Employee:
id:
- 456
- 123
employeeCode:
- EMP456
- EMP123
name:
- Jane
- John
department:
- HR
- IT
Error:
message:
- 'BAD REQUEST - Invalid field: department, value: 123 <number>, expected: <string>'
- Note how the hierarchy of the dictionary reflects the schema outlined in the OpenAPI specification.
- Each key in the dictionary aligns with a property in the schema, while the values represent the possible values for that property.
We have not included the invalid value of department in the bad-request example. This filtering is not restricted to 4xx examples,
the command will include only valid values in the dictionary, allowing it to be executed even with invalid examples.
Dictionary with Contract Testing
The Dictionary can be utilized in contract testing, allowing Specmatic to use the values defined in the dictionary when generating requests for tests. To illustrate this process, we will use the previous specification and dictionary as an example.
For the moment, we will remove the employees_examples directory to observe how contract testing operates without examples.
Run the tests
Now to execute contract tests on the specification using the dictionary a service is required, we will utilize service-virtualization for this purpose.
-
java -jar specmatic.jar stub employees.yaml -
npx specmatic stub employees.yaml -
docker run --rm --network host -v "$(pwd)/employees.yaml:/usr/src/app/employees.yaml" -v "$(pwd)/employees_dictionary.yaml:/usr/src/app/employees_dictionary.yaml" specmatic/specmatic stub "employees.yaml"
Next, execute the contract tests by running the following command:
-
java -jar specmatic.jar test employees.yaml -
npx specmatic test employees.yaml -
docker run --rm --network host -v "$(pwd)/employees.yaml:/usr/src/app/employees.yaml" -v "$(pwd)/employees_dictionary.yaml:/usr/src/app/employees_dictionary.yaml" specmatic/specmatic test "employees.yaml"
We can now examine the request sent to the service by reviewing the logs.
POST /employees
Accept-Charset: UTF-8
Accept: */*
Content-Type: application/json
{
"name": "James",
"department": "IT"
}
The values from the dictionary are used in the requests. Keep in mind that these values are selected in a pseudo-random manner from the list, so you may encounter different values each time.
Generative Tests
As it’s evident that only valid values can be included in the dictionary. hence, generative tests will ignore the values in the dictionary for the key being mutated. The other keys will still retrieve values from the dictionary if available; otherwise, random values will be generated.
For instance, if you execute the specification with generative tests enabled, one of the request will appear as follows:
POST /employees
Accept-Charset: UTF-8
Accept: */*
Content-Type: application/json
{
"name": null,
"department": "IT"
}
In this case, the key name is being mutated, which results in the value from the dictionary being disregarded.
While the values for department is still being retrieved from the dictionary
Dictionary with Service Virtualization
The Dictionary can be utilized with service virtualization, where Specmatic will leverage the values in the dictionary to generate responses for incoming requests. To illustrate this functionality, let’s consider the previous specification and dictionary as an example. We will remove the employees_examples directory to observe how service virtualization functions without examples.
Run Service Virtualization
To start service virtualization, use the following command:
-
java -jar specmatic.jar stub employees.yaml -
npx specmatic stub employees.yaml -
docker run --rm -p 9000:9000 -v "$(pwd)/employees.yaml:/usr/src/app/employees.yaml" -v "$(pwd)/employees_dictionary.yaml:/usr/src/app/employees_dictionary.yaml" specmatic/specmatic stub "employees.yaml"
Making Requests
Execute the following curl command:
-
curl -X POST -H "Content-Type: application/json" -d "{\"name\":\"Jamie\",\"department\":\"IT\"}" http://localhost:9000/employees -
Invoke-RestMethod -Uri "http://localhost:9000/employees" -Method Post -Headers @{"Content-Type"="application/json"} -Body '{"name":"Jamie","department":"IT"}'
You’ll get the following response:
{
"id": 123,
"employeeCode": "EMP123",
"name": "Jane",
"department": "IT"
}
The values from the dictionary are used in the responses. Keep in mind that these values are selected in a pseudo-random manner from the list, so you may encounter different values each time.
Dictionary with Examples
You can also utilize dictionary with the example commands and the examples interactive server, allowing values to be retrieved from the dictionary for both request and response generation of examples, refer to External Examples for more details.