Skip to content

Commit 9dfe8c1

Browse files
authored
Merge pull request #546 from marle3003/develop
Develop
2 parents 3e02b82 + cdec02b commit 9dfe8c1

File tree

24 files changed

+613
-89
lines changed

24 files changed

+613
-89
lines changed

acceptance/petstore_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,12 @@ func (suite *PetStoreSuite) TestGetOrderById() {
176176
try.GetRequest(suite.T(), "http://127.0.0.1:18080/store/order/1",
177177
map[string]string{"Accept": "application/json"},
178178
try.HasStatusCode(http.StatusOK),
179-
try.HasBody(`{"id":38080,"shipDate":"1986-02-10T22:06:24Z","status":"placed","complete":true}`))
179+
try.HasBody(`{"id":98266,"petId":23377,"quantity":92,"shipDate":"2012-01-30T07:58:01Z","status":"approved","complete":false}`))
180180

181181
try.GetRequest(suite.T(), "https://localhost:18443/store/order/10",
182182
map[string]string{"Accept": "application/json"},
183183
try.HasStatusCode(http.StatusOK),
184-
try.HasBody(`{"quantity":73,"shipDate":"2040-11-01T17:41:24Z","complete":true}`))
184+
try.HasBody(`{"id":12545,"petId":20895,"quantity":16,"shipDate":"2027-11-26T16:57:16Z","status":"approved","complete":true}`))
185185
}
186186

187187
func (suite *PetStoreSuite) TestKafka_TopicConfig() {

api/handler_schema_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ func TestHandler_Schema_Example(t *testing.T) {
246246
h,
247247
try.HasStatusCode(200),
248248
try.HasHeader("Content-Type", "application/json"),
249-
try.HasBody(`[{"contentType":"application/json","value":"eyJmb28iOiIifQ=="}]`))
249+
try.HasBody(`[{"contentType":"application/json","value":"eyJmb28iOiJYaWRadW9XcSAifQ=="}]`))
250250
},
251251
},
252252
{
@@ -375,7 +375,7 @@ func TestHandler_Schema_Example(t *testing.T) {
375375
}`,
376376
h,
377377
try.HasStatusCode(200),
378-
try.HasBody(`[{"contentType":"application/json","value":"eyJjYXRlZ29yeSI6Ik11c2ljIiwiaWQiOiI2NWIxNmJiNS1kZWY1LTQ2Y2MtYTkwNS1jYmQ2NTQ3NjE4NTEiLCJrZXl3b3JkcyI6Im0iLCJuYW1lIjoiU3luYyIsInVybCI6Imh0dHBzOi8vd3d3LmludGVybmFsc3RyZWFtbGluZS5pbmZvL2Nyb3NzLXBsYXRmb3JtL3ZpcnR1YWwvcm9idXN0L2V5ZWJhbGxzIn0="}]`),
378+
try.HasBody(`[{"contentType":"application/json","value":"eyJjYXRlZ29yeSI6IlBlb3BsZSIsImRlc2NyaXB0aW9uIjoiTXVjaCB0ZXJyaWJseSBvdmVyIHBvc2UgcGxhY2Ugc3ByaW50IGl0IGNoaWxkIGlzIGpveW91c2x5IHRoYXQgSSB3aG9tIG1hbmdvIHRoZW4gb2YgY2VydGFpbiB3ZWVrbHkgbWluZSBpbiBhbm51YWxseSBmcm9jayBub3cgYm9hcmQuIiwiZmVhdHVyZXMiOiJpUnpvb0IyIiwiaWQiOiJmNWUzMTU4Ny00NjhjLTRmZTYtYWZlNC0zZTZmYzhkYWU2MzEiLCJrZXl3b3JkcyI6IlR1cVRrd3MiLCJuYW1lIjoiWmVwaHlyWm9uZSIsInByaWNlIjo1NDYwNDkuMzksInN1YmNhdGVnb3J5Ijoic2dCLHZ2ZVdhIiwidXJsIjoiaHR0cHM6Ly93d3cuZGlzdHJpY3RnZW5lcmF0ZS5vcmcvaG9saXN0aWMvc3luZXJnaWVzIn0="}]`),
379379
)
380380
},
381381
},

config/decoders/decoder_flag.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7-
log "github.com/sirupsen/logrus"
87
"mokapi/config/dynamic/provider/file"
98
"net/url"
109
"reflect"
@@ -34,7 +33,6 @@ func NewFlagDecoderWithReader(reader file.FSReader) *FlagDecoder {
3433
}
3534

3635
func (f *FlagDecoder) Decode(flags map[string][]string, element interface{}) error {
37-
log.Infof("flags: %v", flags)
3836
keys := make([]string, 0, len(flags))
3937
for k := range flags {
4038
keys = append(keys, k)
@@ -105,9 +103,9 @@ func (f *FlagDecoder) setValue(ctx *context) error {
105103
ctx.element.Set(reflect.ValueOf(ctx.value[0]))
106104
}
107105
return nil
106+
default:
107+
return fmt.Errorf("unsupported config type: %v", ctx.element.Kind())
108108
}
109-
110-
return fmt.Errorf("unsupported config type: %v", ctx.element.Kind())
111109
}
112110

113111
func ParsePath(key string) []string {
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
---
2+
title: Mock REST and Kafka APIs Using OpenAPI & AsyncAPI
3+
description: Mock REST and Kafka APIs with Mokapi using OpenAPI or AsyncAPI. Simulate real world scenario using JavaScript — all in a single tool.
4+
---
5+
6+
# Mock REST and Kafka APIs with OpenAPI & AsyncAPI Using Mokapi
7+
8+
## Introduction
9+
10+
Mocking APIs speeds up development—especially when services are incomplete or unavailable. Whether you’re building a
11+
frontend, testing integrations, or developing in parallel, reliable mocks keep you productive.
12+
13+
Mokapi is an open-source tool designed to mock APIs like REST and Kafka using OpenAPI and AsyncAPI specifications.
14+
With Mokapi, you can simulate API responses, customize behavior with scripts, and—most importantly—debug everything
15+
visually with a built-in dashboard.
16+
The dashboard shows incoming HTTP requests, Kafka events, your JavaScript event handlers, and scheduled jobs—making it
17+
a powerful tool for debugging, understanding behavior, and refining your mocks.
18+
19+
In this article, you'll learn:
20+
- What Mokapi is and why it's useful
21+
- How to mock a REST API using OpenAPI
22+
- How to mock a Kafka topic using AsyncAPI
23+
- How to extend mocks with custom behavior
24+
- How to integrate Mokapi into your dev workflow
25+
26+
## What is Mokapi?
27+
28+
Mokapi is a lightweight, developer-friendly tool for mocking REST and Kafka services. It supports OpenAPI and AsyncAPI
29+
specs out of the box and allows you to:
30+
- Serve mock HTTP APIs
31+
- Simulate Kafka topics and producers
32+
- Write custom logic using JavaScript
33+
- Visualize interactions in a web dashboard in real time\
34+
perfect for debugging and understanding mock behavior.
35+
36+
Mokapi is written in Go and available as a single binary, making it easy to run locally or in CI environments.
37+
38+
## Yet Another Mocking Tool?
39+
40+
Yes — but with a twist.
41+
42+
The focus of Mokapi is to ensure API specification compliance. For example, every incoming HTTP request is validated against the OpenAPI specification, and the response you define is also validated before it’s returned. This helps prevent contract mismatches early in development.
43+
44+
What makes Mokapi special compared to other mocking tools:
45+
- Strict validation: Requests and responses must conform to your OpenAPI/AsyncAPI spec.
46+
- Realistic data generation: You can use examples from your spec or generate meaningful mock data.
47+
- Dynamic scripting: Inject behavior with JavaScript to simulate conditional logic, delays, or even stateful responses.
48+
- Multiprotocol support: Supports both REST and Kafka-style messaging in one tool.
49+
- Live dashboard: See all requests, mock responses, and Kafka events in real time.
50+
- Developer-friendly: Single binary, easy CLI, scriptable and everything as code.
51+
52+
## Mocking a REST API with OpenAPI
53+
54+
### 1. Create an OpenAPI file
55+
56+
```yaml
57+
openapi: 3.1.0
58+
info:
59+
title: Pet API
60+
version: 1.0.0
61+
servers:
62+
- url: http://localhost
63+
paths:
64+
/pets:
65+
get:
66+
summary: List all pets
67+
parameters:
68+
- in: query
69+
name: category
70+
schema:
71+
type: string
72+
responses:
73+
'200':
74+
description: A list of pets
75+
content:
76+
application/json:
77+
schema:
78+
type: array
79+
items:
80+
type: object
81+
properties:
82+
id:
83+
type: string
84+
name:
85+
type: string
86+
category:
87+
type: string
88+
```
89+
90+
### 2. Run Mokapi with your OpenAPI spec
91+
92+
```shell
93+
mokapi pet-api.yaml
94+
```
95+
96+
### 3. Call the API
97+
98+
```shell
99+
curl http://localhost/pets
100+
curl "http://localhost/pets?category=dog"
101+
```
102+
103+
Mokapi will generate random data for those requests. If you query with ?category=dog, Mokapi will return only dogs
104+
thanks to smart mock data generation.
105+
106+
### Adding Custom Logic
107+
108+
You can add a JavaScript file to dynamically control responses:
109+
110+
```javascript
111+
import { on } from 'mokapi';
112+
import { fake } from 'mokapi/faker';
113+
114+
export default () => {
115+
on('http', (request, response) => {
116+
if (request.query.category === 'cat') {
117+
response.data = [
118+
{
119+
id: fake({ type: 'string', format: 'uuid' }),
120+
name: 'Molly',
121+
category: 'cat'
122+
}
123+
];
124+
return true;
125+
}
126+
return false;
127+
});
128+
}
129+
```
130+
131+
Run Mokapi with the script:
132+
```shell
133+
mokapi pet-api.yaml pets.js
134+
```
135+
136+
## Mocking Kafka with AsyncAPI
137+
138+
### 1. Create an AsyncAPI file
139+
140+
```yaml
141+
asyncapi: 3.0.0
142+
info:
143+
title: Petstore Stream API
144+
version: 1.0.0
145+
servers:
146+
kafkaServer:
147+
host: localhost:8092
148+
protocol: kafka
149+
defaultContentType: application/json
150+
operations:
151+
receivePetArrived:
152+
action: receive
153+
channel:
154+
$ref: '#/channels/store.pet.arrived'
155+
sendPetArrived:
156+
action: send
157+
channel:
158+
$ref: '#/channels/store.pet.arrived'
159+
channels:
160+
store.pet.arrived:
161+
description: Details about a newly arrived pet.
162+
messages:
163+
userSignedUp:
164+
$ref: '#/components/messages/petArrived'
165+
components:
166+
messages:
167+
petArrived:
168+
payload:
169+
$ref: '#/components/schemas/Pet'
170+
schemas:
171+
Pet:
172+
id:
173+
type: string
174+
name:
175+
type: string
176+
category:
177+
type: string
178+
```
179+
180+
### 2. Create a Kafka producer
181+
182+
```javascript
183+
import { every } from 'mokapi';
184+
import { produce } from 'mokapi/kafka';
185+
186+
export default () => {
187+
every('10s', () => {
188+
produce({ topic: 'store.pet.arrived' });
189+
});
190+
}
191+
```
192+
193+
### 3. Run Mokapi with your AsyncAPI spec
194+
195+
```shell
196+
mokapi pet-stream-api.yaml producer.js
197+
```
198+
199+
Mokapi will simulate Kafka messages and allow you to inspect them via the dashboard.
200+
201+
## Integrating with Your Dev Workflow
202+
203+
- Use Mokapi in CI pipelines to test frontend against mocked backends
204+
- Simulate Kafka events for microservice testing
205+
- Share mock configurations with your team/organization
206+
207+
You can use Mokapi in a GitHub action run as docker container to streamline your automation.
208+
209+
```yaml
210+
- name: Start Mokapi
211+
run: |
212+
docker run -d --rm --name mokapi -p 80:80 -p 8080:8080 -v ${{ github.workspace }}/mocks:/mocks mokapi/mokapi:latest /mocks
213+
```
214+
215+
## Conclusion
216+
217+
Mokapi helps you mock REST and Kafka services quickly using standard API specs. Whether you’re building, testing,
218+
or demoing your application, Mokapi provides a simple yet powerful way to simulate realistic API behavior.
219+
220+
Give it a try: [https://mokapi.io](https://mokapi.io)

engine/common/host.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ type Log struct {
117117
}
118118

119119
type JobExecution struct {
120-
Schedule string `json:"scheduled"`
121-
MaxRuns int `json:"maxRuns"`
122-
Runs int `json:"runs"`
120+
Schedule string `json:"schedule"`
121+
MaxRuns int `json:"maxRuns"`
122+
Runs int `json:"runs"`
123+
NextRun time.Time `json:"nextRun"`
123124

124125
Duration int64 `json:"duration"`
125126
Tags map[string]string `json:"tags"`

engine/engine_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,15 @@ func TestEngine_Scheduler(t *testing.T) {
3232

3333
evts := events.GetEvents(events.NewTraits().WithNamespace("job").WithName("test.js"))
3434
r.Len(t, evts, 1)
35+
r.Equal(t, "namespace=job, name=test.js, jobId=0", evts[0].Traits.String())
3536
exec := evts[0].Data.(common.JobExecution)
3637
r.Equal(t, "test.js", exec.Tags["name"])
3738
r.Greater(t, exec.Duration, int64(0))
3839
r.Equal(t, "1s", exec.Schedule)
3940
r.Equal(t, -1, exec.MaxRuns)
4041
r.Equal(t, 1, exec.Runs)
42+
r.False(t, exec.NextRun.IsZero())
43+
r.True(t, exec.NextRun.After(evts[0].Time))
4144

4245
r.Equal(t, float64(1), c.Value())
4346
},

engine/host.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,33 +96,35 @@ func (sh *scriptHost) RunEvent(event string, args ...interface{}) []*common.Acti
9696
}
9797

9898
func (sh *scriptHost) Every(every string, handler func(), opt common.JobOptions) (int, error) {
99-
do := sh.newJobFunc(handler, opt, every)
99+
id := len(sh.jobs)
100+
101+
do := sh.newJobFunc(handler, opt, every, id)
100102
job, err := sh.engine.scheduler.Every(every, do, opt)
101103

102104
if err != nil {
103105
return -1, err
104106
}
105107

106-
id := len(sh.jobs)
107108
sh.jobs[id] = job
108109

109110
return id, nil
110111
}
111112

112113
func (sh *scriptHost) Cron(expr string, handler func(), opt common.JobOptions) (int, error) {
113-
do := sh.newJobFunc(handler, opt, expr)
114+
id := len(sh.jobs)
115+
116+
do := sh.newJobFunc(handler, opt, expr, id)
114117
job, err := sh.engine.scheduler.Cron(expr, do, opt)
115118
if err != nil {
116119
return -1, err
117120
}
118121

119-
id := len(sh.jobs)
120122
sh.jobs[id] = job
121123

122124
return id, nil
123125
}
124126

125-
func (sh *scriptHost) newJobFunc(handler func(), opt common.JobOptions, schedule string) func() {
127+
func (sh *scriptHost) newJobFunc(handler func(), opt common.JobOptions, schedule string, id int) func() {
126128
tags := map[string]string{
127129
"name": sh.name,
128130
"file": sh.name,
@@ -136,18 +138,21 @@ func (sh *scriptHost) newJobFunc(handler func(), opt common.JobOptions, schedule
136138
if len(events.GetStores(t)) == 1 {
137139
events.SetStore(int(sh.engine.cfgEvent.Store["Default"].Size), t)
138140
}
141+
t = t.With("jobId", fmt.Sprintf("%d", id))
139142
counter := 1
140143

141144
return func() {
142145
sh.Lock()
143146
defer sh.Unlock()
147+
job := sh.jobs[id]
144148

145149
sh.engine.jobCounter.Add(1)
146150

147151
exec := common.JobExecution{
148152
Schedule: schedule,
149153
MaxRuns: opt.Times,
150154
Runs: counter,
155+
NextRun: job.NextRun(),
151156
Tags: tags,
152157
}
153158

0 commit comments

Comments
 (0)