Where OpenAPI meets KarateDSL Server Side Features for REST API mocking:
- Powerful yet simple stateful mocks using KarateDSL. You can even generate those mocks from your OpenAPI definition using KarateIDE vscode extension.
- Request/response validation from your OpenAPI schemas.
- Declarative stateless mocks from your OpenAPI examples with
'x-apimock-when'
. - Flexible and powerful dynamic data generators for your OpenAPI examples with
x-apimock-transform
(both built-in and custom defined). - Use openapi examples to populate your karate mocks initial data with
x-apimock-karate-var
andx-apimock-seed
.
Checkout KarateIDE vscode extension for a powerful testing and mocking user experience for KarateDSL.
This is basically a wrapper around KarateDSL mock server with some useful features from OpenAPI examples.
OpenAPI definitions are used to validate request/responses and to serve dynamic OpenAPI examples:
- Request/response validation thanks to org.openapi4j:openapi-operation-validator
- Choose OpenAPI examples with
'x-apimock-when'
: you can use any valid javascript and karate expression that returns a boolean. - Built-in Data Generators inline in your examples:
uuid()
,now('dd/MM/yyyy')
,date('dd/MM/yyy', +/-n[d|h])
,sequenceNext()
- Use
'x-apimock-transform'
when generator tags are not compatible with your OpenAPI linters. - Custom Data Generators: define your own data generators using Javascript or even Java right inside you karate features. They will be available also for OpenAPI examples
- Populates karate features data sets from OpenAPI
'#/components/examples/'
. - Seed (multiply) mock data from a few OpenAPI examples.
With KarateDSL you can create:
- Simple yet powerful stateful mocks. See official KarateDSL Mocks for details.
- Delegating validations to OpenAPI makes karate mocks even simpler.
- Reusing OpenAPI examples as mock data sets. Compatible with any favorite graphical OpenAPI editing tool, makes it easy to edit and lint your mocking data sets.
- All this is just a wrapper and custom hooks around KarateDSL MockServer listening for requests and serving responses.
- Before requests are passed into Karate features, a custom hook validates request payload against provided OpenAPI definition file, responding with a
400 status code
error in case it fails validation. - If request passes validation, then it is routed to KarateDSL server side features.
- If Karate matches one scenario it produces a response.
- If no Karate scenario matches for your request, then a custom hook will search for examples in OpenAPI definition file and will filter them using
x-apimock-when
. This when clause uses the same functions and functionality as KarateDSL scenario names selection. If no example matches this when clause the it will send a 404 status code. - OpenAPI examples are processed for interpolated function tags using
{{ }}
. For instance{{ uuid() }}
will be replaced with the output ofuuid()
generator. You can configure any custom generator defining them in the Background section of you Karate mock features.
- Last, all responses are validated against OpenAPI schema and will send a 400 in case validation fails.
Karate mocks allows you to create stateful mocks with little effort, but you can also create dynamic stateless mocks using just OpenAPI response examples.
You can use just OpenAPI definition to create complex dynamic stateless mocks.
- Use
x-apimock-when
to select which response example you want to serve. You can use any valid Javascript (ES2021) expression that returns a boolean. - Use
{{ }}
to interpolate generator tags. There are some built-in generators (uuid, now, date, sequenceNext), but you can also add any custom generator using the Background section of your karate mocks. - Use
x-apimock-transform
when generator tags are not compatible with your OpenAPI linters.
You can use any valid Javascript (ES2021) expression with all karate variables (request, pathParams, requestParams, requestHeader,...) and helpers (paramExists(), paramValue(), headerContains(), bodyPath(),...) available for you.
See [karate documentation]((https://github.com/karatelabs/karate/tree/master/karate-netty#request) for more details .
Just see this example to grasp an idea of what you can do:
responses:
'200':
description: successful operation
content:
application/xml:
schema:
"$ref": "#/components/schemas/Pet"
application/json:
schema:
"$ref": "#/components/schemas/Pet"
examples:
pet-1:
summary: Dynamic Pet
x-apimock-when: pathParams.petId > 0
x-apimock-transform:
id: pathParams.petId
status: "Math.random() >= 0.5? 'sold' : 'available'"
creationDate: 'date("dd/MM/yyyy", "-1d")'
value:
id: 0
name: 'DOG {{Math.random()}}'
category:
id: 0
name: DOG
photoUrls: []
tags:
- id: 0
name: 'name'
status: sold
You can populate karate mocks intitial data leveraging #/components/examples/ using x-apimock-karate-var
, x-apimock-seed
, and x-apimock-transform
to generate dynamic data right from your OpenAPI definition.
examples:
karate-pets:
summary: Pets for karate mocks dataset
x-apimock-karate-var: pets
x-apimock-seed: 20
x-apimock-transform:
$[*].id: sequenceNext()
$[*].status: "Math.random() >= 0.5? 'sold' : 'available'"
value:
- id: 0
name: 'Dog Name {{Math.random()}}'
category:
id: 1
name: DOG
status: sold
- id: 0
name: 'Cat Name {{Math.random()}}'
category:
id: 2
name: CAT
tags:
- id: 0
name: 'Cat'
status: sold
This helps keeping your karate mock features to a minimum logic.
@mock
Feature: PetMock Mock
Background:
* configure cors = true
* configure responseHeaders = { 'Content-Type': 'application/json' }
# this array will be populated directly from openapi.yml#/components/examples/pets
* def pets = []
@getPetById
Scenario: methodIs('get') && pathMatches('/pet/{petId}')
* def response = pets.find(pet => pet.id == pathParams.petId)
* def responseStatus = response? 200 : 404
@addPet
Scenario: methodIs('post') && pathMatches('/pet')
* def pet = request
* pet.id = sequenceNext()
* pets.push(pet)
* def response = pet
* def responseStatus = 200
Checkout this complete CRUD Example
- OpenAPI definition with examples data
- Complete Karate PetMock
- JUnit CRUD test using an OpenAPI client
<dependency>
<groupId>io.github.zenwave360</groupId>
<artifactId>zenwave-apimock</artifactId>
<version>${apimock.version}</version>
</dependency>
java -cp "apimock.jar;karate-1.2.0.jar" io.github.apimock.Main -o openapi.yml -m Mock.feature -p 3000 -P context/path -W
If you see an error like this, make sure you have added karate fat jar dependency in your classpath, make sure it's karate fat jar.
Exception in thread "main" java.lang.NoClassDefFoundError: picocli/CommandLine
at io.github.apimock.Main.main(Main.java:46)
Caused by: java.lang.ClassNotFoundException: picocli.CommandLine
In JUnit tests, you can mock your REST services integrations starting a MockServer on a random port and pointing clients to this local implementation:
public class RestClientExampleTest {
io.github.apimock.MockServer server;
@Before
public void setup() throws Exception {
server = MockServer.builder()
.openapi("classpath:petstore/petstore-openapi.yml")
.features("classpath:petstore/mocks/PetMock/PetMock.feature")
.pathPrefix("api/v3")
.http(0).build();
}
@Test
public void testRestWithMockServer() {
PetApi petApiClient = new PetApi();
petApiClient.getApiClient().setBasePath("http://localhost:" + server.getPort() + "/api/v3");
PetDto pet = petApiClient.getPetById(1L);
Assert.assertNotNull(pet);
}
@After
public void tearDown() throws Exception {
server.stop();
}
}