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 lookup 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 value
or 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
name
field is supplied as asingle value
and will be used directly. - The
age
field is supplied as alist of values
, from which either20
,30
, or40
will bepseudo-random
selected.
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-randomly
selected 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_name
is 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
john
namedemployee_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
jane
namedemployee_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 thedepartment
field:
{ "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.