gRPC Commercial
Introduction
Specmatic supports service virtualization, contract testing and backward compatibility for gRPC APIs, similar to its support for REST (OpenAPI) and SOAP APIs. This document will guide you through using Specmatic for gRPC services.
What Can You Achieve with Specmatic's gRPC Support?
With Specmatic gRPC support you will be to leverage your proto files as executable contracts.
- Intelligent Service Virtualization: Stub out gRPC services for testing and development
- Contract Testing: Validate requests and responses against your proto files
- Backward Compatibility Checks: Compare two versions of your proto files to identify breaking changes without writing any code
- Central Contract Repo: Store your proto files in central Git repo which will serve as single source of truth for both providers and consumers
- API resiliency : Generate negative and edge cases to verify the resiliency of your API implementation again based on your proto files and validations rules within them.
These capabilities enable you to develop and test gRPC-based applications more efficiently and with greater confidence in your API contracts.
Quick Start
Here is a sample project which has detailed animated architecture diagram along with explanation about how we are isolating the System Under Test during Contract Tests.
Alternatively if you have Java (JDK 17 and above) on your machine, you can run the contract tests using gradle also.
Detailed explanation
Using your proto files as your API Contracts
Before you can use Specmatic with your gRPC services, you need to define your service contracts using Protocol Buffer (.proto) files. Here's how to manage these contracts:
- Store your .proto files in a central repository for easy access and version control.
- Create a
specmatic.yamlfile in the root of your project to reference these contracts. Here are examples for different configuration versions:
version: 3
dependencies:
services:
- service:
$ref: "#/components/services/yourService"
runOptions:
$ref: "#/components/runOptions/yourServiceMock"
components:
sources:
yourContracts:
git:
url: https://your-central-contract-repo.com
branch: main
services:
yourService:
description: Your gRPC Service
definitions:
- definition:
source:
$ref: "#/components/sources/yourContracts"
specs:
- spec:
id: yourServiceSpec
path: /path/to/your/service.proto
runOptions:
yourServiceMock:
protobuf:
type: mock
specs:
- spec:
id: yourServiceSpec
host: localhost
port: 9000
importPaths:
- ../
protocVersion: 3.23.4
Make sure to update the repository URLs, paths, and configuration to reflect your actual contract repository and .proto file locations.
Support for Local Imports in Proto Files
Specmatic gRPC also supports proto files that import other proto files locally. There are two scenarios:
-
Locally imported file in the same folder:
- If your proto file imports another proto file that is present in the same directory, Specmatic gRPC will automatically resolve the import. No additional configuration is required.
-
Locally imported file in a different folder:
- If your proto file imports another proto file from a different directory, Specmatic gRPC will not resolve the import automatically. You need to specify the import path so Specmatic can locate the imported file.
Config Approach: Specify the import paths in your specmatic.yaml configuration file under importPaths. For example:
version: 3
components:
runOptions:
yourServiceMock:
protobuf:
type: mock
specs:
- spec:
id: yourServiceSpec
host: localhost
port: 9000
importPaths:
- ../external-protos
- path/to/imported/protos
protocVersion: 3.23.4
This ensures that all locally imported proto files are correctly resolved during contract testing and service virtualization.
Note: If you encounter import errors, verify that the import paths are correctly set and that all referenced proto files are accessible in the specified directories.
Example: Local Import Usage
Suppose you have the following directory structure:
contracts/
order.proto
common/
types.proto
And your order.proto file contains:
syntax = "proto3";
import "common/types.proto";
package com.store.order;
// ... service and message definitions ...
If you run Specmatic gRPC from the contracts directory, it will automatically resolve the import if common/types.proto is inside contracts/common.
If types.proto is located outside the contracts directory, for example:
contracts/
order.proto
external-protos/
types.proto
Then you need to specify the import path:
Config Approach: Add the import path to your specmatic.yaml:
version: 3
components:
runOptions:
yourServiceMock:
protobuf:
type: mock
specs:
- spec:
id: yourServiceSpec
host: localhost
port: 9000
importPaths:
- ../external-protos
protocVersion: 3.23.4
This ensures Specmatic gRPC can locate and resolve the imported types.proto file during contract testing and service virtualization.
Support for Well-Known Remote Imports
Specmatic gRPC also supports some well-known remote imports, such as google/protobuf/Empty.proto and other standard Google protobuf types. These files are automatically resolved by Specmatic gRPC and do not require you to download or configure them manually.
For example, you can use the following import in your proto file:
import "google/protobuf/Empty.proto";
You can then use the Empty message type in your service definitions:
service HealthCheck {
rpc Ping(google.protobuf.Empty) returns (google.protobuf.Empty);
}
Specmatic gRPC will automatically resolve these well-known types during contract testing and service virtualization. No additional configuration or import path setup is required for these standard imports.
Note: If you use other remote or custom proto files that are not well-known, you may need to provide them locally and use the import path configuration as described in the previous section.
Using Externalized Examples as Test/Stub Data for gRPC in Contract Tests and Service Virtualization
Suppose you have a gRPC service definition as shown below:
syntax = "proto3";
package com.store.order.bff;
service OrderService {
rpc createOrder (CreateOrderRequest) returns (CreateOrderResponse);
}
message CreateOrderRequest {
int32 productId = 1;
int32 count = 2;
}
message CreateOrderResponse {
int32 id = 1;
}
To provide example values for testing and stubbing, you can create a JSON file containing data for the createOrder method:
{
"fullMethodName": "com.store.order.bff.OrderService/createOrder",
"request": {
"productId": 10,
"count": 8
},
"response": {
"id": 21
}
}
You can also define example data to simulate error scenarios for the same method. For example:
{
"fullMethodName": "com.store.order.bff.OrderService/createOrder",
"request": {
"productId": 50000,
"count": 8
},
"response": {
"errorCode": 5,
"errorMessage": "Product with ID 50000 was not found"
}
}
Here, the errorCode field should use one of the standard google.rpc.Code status codes.
These example files should be stored in a directory named <file_name_without_extension>_examples, located in the same directory as the corresponding .proto file. This structure keeps your example data organized and easily discoverable alongside the service definitions.
Alternatively, you can configure the example directories in your configuration file, programmatically, or via command-line arguments, depending on your setup and deployment needs.
Config Approach: Specify example directories in your specmatic.yaml configuration file under data.examples.directories at the service level. For example:
version: 3
dependencies:
services:
- service:
$ref: "#/components/services/yourService"
runOptions:
$ref: "#/components/runOptions/yourServiceMock"
data:
examples:
- directories:
- path/to/dir1
- path/to/dir2
components:
sources:
yourContracts:
git:
url: https://your-central-contract-repo.com
branch: main
services:
yourService:
description: Your gRPC Service
definitions:
- definition:
source:
$ref: "#/components/sources/yourContracts"
specs:
- spec:
id: yourServiceSpec
path: /path/to/your/service.proto
runOptions:
yourServiceMock:
protobuf:
type: mock
specs:
- spec:
id: yourServiceSpec
host: localhost
port: 9000
importPaths:
- ../
protocVersion: 3.23.4
Understanding the Example Format
Each example file follows a simple, structured JSON format:
- The top-level object contains three keys:
fullMethodName,request, andresponse. fullMethodNamespecifies the complete gRPC method name, including the package, service, and method.requestrepresents the input message fields sent to the service.responserepresents the expected output or error response from the service.
This format makes it straightforward to create reusable test and stub data that works seamlessly for both contract testing and service virtualization. It’s also designed to be human-readable, making it easy to copy real request/response data directly from logs or runtime traces.
Note: You can refer to this example for a standard scenario, and this example for an error scenario. Try running the contract test to see how Specmatic uses these examples in action.
Using the Docker Image
So far in the above explanation the sample project is invoking Specmatic gRPC support programmatically. However, if you wish to run the same from CLI then below Docker image wraps the same Specmatic gRPC capabilities.
Also, the Specmatic gRPC Docker image, by nature, is completely language and tech stack agnostic.
Starting the Stub Service
To start the stub/service virtualization service, use the appropriate command based on your configuration version:
docker run --network host -v "$(pwd):/usr/src/app" specmatic/enterprise mock
This command mounts your specmatic.yaml file and starts the stub service. All configuration (host, port, import paths, etc.) is read from the specmatic.yaml file.
Running Tests
To run tests, update your specmatic.yaml to include the system under test configuration:
version: 3
systemUnderTest:
service:
$ref: "#/components/services/yourService"
runOptions:
$ref: "#/components/runOptions/yourServiceTest"
dependencies:
services:
- service:
$ref: "#/components/services/dependencyService"
runOptions:
$ref: "#/components/runOptions/dependencyServiceMock"
components:
sources:
yourContracts:
git:
url: https://your-central-contract-repo.com
branch: main
services:
yourService:
description: Your gRPC Service
definitions:
- definition:
source:
$ref: "#/components/sources/yourContracts"
specs:
- spec:
id: yourServiceSpec
path: /path/to/your/your_service.proto
dependencyService:
description: Dependency gRPC Service
definitions:
- definition:
source:
$ref: "#/components/sources/yourContracts"
specs:
- spec:
id: dependencySpec
path: /path/to/your/dependency_service.proto
runOptions:
yourServiceTest:
protobuf:
type: test
specs:
- spec:
id: yourServiceSpec
host: localhost
port: 8080
importPaths:
- ../
protocVersion: 3.23.4
dependencyServiceMock:
protobuf:
type: mock
specs:
- spec:
id: dependencySpec
host: localhost
port: 9000
importPaths:
- ../
protocVersion: 3.23.4
To run tests against your service, use the appropriate command based on your configuration version:
docker run --network host -v "$(pwd):/usr/src/app" specmatic/enterprise test
This command mounts your specmatic.yaml file and runs tests against your service. All configuration (host, port, import paths, etc.) is read from the specmatic.yaml file.
Proto 3 (required, optional) and Proto Validate
Proto 3 dropped required fields. Please see GitHub discussion for more details.
Thereby it is hard to communicate constraints such as required, ranges (gte, lte, etc.).
So Specmatic gRPC support is designed to use add on validation mechanisms like protovalidate. Please refer to our sample projects section below for more details on how this is being used.
Configuring Request Timeout
By default, Specmatic gRPC uses a request timeout of 5 seconds for contract testing. This means for a given request if it takes longer than 5 seconds to receive a response, Specmatic will consider it as a failure.
You would see a similar error in the logs:
[Specmatic gRPC] Received response:
{"errorCode":4,"errorMessage":"DEADLINE_EXCEEDED: CallOptions deadline exceeded after 2.997609209s. Name resolution delay 0.000000000 seconds. [closed=[], open=[[buffered_nanos=47597125, remote_addr=10.211.55.5/10.211.55.5:9000]]]"}
[Specmatic gRPC] Result: FAILED
In such cases, you should increase the requestTimeout. You can specify this in the Specmatic config as follows:
version: 3
systemUnderTest:
service:
$ref: "#/components/services/yourService"
runOptions:
$ref: "#/components/runOptions/yourServiceTest"
components:
sources:
yourContracts:
git:
url: https://github.com/your-org/your-contracts.git
branch: main
services:
yourService:
description: Your gRPC Service
definitions:
- definition:
source:
$ref: "#/components/sources/yourContracts"
specs:
- spec:
id: yourSpec
path: path/to/your/grpc/proto/file/spec.proto
runOptions:
yourServiceTest:
protobuf:
type: test
specs:
- spec:
id: yourSpec
host: localhost
port: 9090
importPaths:
- ../
protocVersion: 3.23.4
requestTimeout: 10000 # Duration in milliseconds to wait for a response before timing out. Default is 5000 ms (5 seconds).
Sample Projects
We have created sample projects to demonstrate how to use Specmatic with gRPC in different languages and scenarios, please follow the link for the latest sample projects
These projects provide practical examples of how to integrate Specmatic gRPC support into your workflow, including setting up stubs, writing tests, and handling different languages, frameworks and running them on CI like GitHub actions.