All checks were successful
Build, Test & Checkstyle / build (push) Successful in 46s
Adding the ability to set relative times and dates to be returned.
## How do test
You can test using the following yaml-file:
```yml
- method: "GET"
path: "/"
response:
status: 200
headers:
Content-Type: "application/json"
body:
description: "Relative date/time placeholder examples"
# ISO formats (default)
currentTime: "{datetime+0d@08:00}"
today: "{date+0d}"
tomorrow: "{date+1d}"
futureDateTime: "{datetime+2d4h30m}"
pastDateTime: "{datetime-1d@15:30}"
# RFC3339 formats
currentTimeRfc: "{datetime+0d@08:00:rfc3339}"
tomorrowRfc: "{datetime+1d@13:00:rfc3339}"
# Custom formats
todayCustom: "{date+0d:MMM dd, yyyy}"
tomorrowCustom: "{date+1d:EEEE, MMMM d}"
futureCustom: "{datetime+1d@15:30:yyyy-MM-dd HH:mm:ss}"
timeOnly: "{datetime+0d@14:30:HH:mm:ss}"
- method: "GET"
path: "/examples"
response:
status: 200
headers:
Content-Type: "application/json"
body:
calendar:
- event: "Morning standup"
time: "{datetime+1d@09:00}"
date: "{date+1d:EEEE, MMM dd}"
- event: "Client meeting"
time: "{datetime+2d@14:30:rfc3339}"
date: "{date+2d}"
- event: "Weekly review"
time: "{datetime+7d@16:00}"
date: "{date+7d:MMM dd, yyyy}"
```
Reviewed-on: #6
Reviewed-by: Andreas Svanberg <andreass@dsv.su.se>
Co-authored-by: nenzen <nenzen@dsv.su.se>
Co-committed-by: nenzen <nenzen@dsv.su.se>
388 lines
10 KiB
Markdown
388 lines
10 KiB
Markdown
# APImposter 🎭
|
|
|
|
**A small, flexible API mock service designed with microservice environments in mind.**
|
|
|
|
---
|
|
|
|
## What is APImposter?
|
|
|
|
APImposter is an easy-to-use service built with Spring Boot, enabling quick mocking of API responses from various systems. It allows you to organize your mock data in YAML files.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
- **Structured Mock Data:** Organize your mock data in YAML files.
|
|
- **Automatic URL Mapping:** Automatically generates URL structure based on your folder hierarchy and YAML file names.
|
|
- **Hot Reloading:** Automatically reloads mock data files when they are changed.
|
|
- **Dynamic Responses and Global Placeholders:** Supports dynamic values in responses based on URL parameters and you can define global placeholders to reuse values across multiple responses and systems.
|
|
- **Conditional Responses:** Return different mock responses based on request data (headers, query, body, or path).
|
|
- **Swagger UI:** Automatically generated Swagger UI from YAML files.
|
|
|
|
---
|
|
|
|
## Mocks Structure
|
|
|
|
Mocks are defined in YAML files and stored in a directory specified via `application.properties`. You can organize them in nested folders:
|
|
|
|
```
|
|
/mocks/
|
|
├── globals.yaml
|
|
├── system1/
|
|
│ ├── users.yaml
|
|
│ └── index.yaml
|
|
└── another-system/
|
|
├── endpoint1.yaml
|
|
└── subdir/
|
|
├── endpoint2.yaml
|
|
└── index.yaml
|
|
```
|
|
|
|
### URL Mapping
|
|
|
|
- `/system1/users.yaml` defines mocks under `/system1/users/...`
|
|
- `/system1/index.yaml` defines mocks under`/system1/...`)
|
|
- Multiple endpoints can be grouped in one file
|
|
- Folders in the YAML file structure become part of the URL path automatically.
|
|
|
|
### Grouped endpoints in `users.yaml`
|
|
|
|
```yaml
|
|
# mocks/system1/users.yaml
|
|
- method: GET
|
|
path: "/"
|
|
response:
|
|
status: 200
|
|
body:
|
|
users:
|
|
- id: 1
|
|
name: "Alice"
|
|
- id: 2
|
|
name: "Bob"
|
|
|
|
- method: GET
|
|
path: "/{id}"
|
|
response:
|
|
status: 200
|
|
body:
|
|
id: "{id:int}"
|
|
name: "User {id}"
|
|
|
|
- method: POST
|
|
path: "/"
|
|
response:
|
|
status: 200
|
|
body:
|
|
message: "User created"
|
|
```
|
|
|
|
### Base-level grouping via `index.yaml`
|
|
|
|
```yaml
|
|
# mocks/system1/index.yaml
|
|
- method: GET
|
|
path: "/"
|
|
response:
|
|
status: 200
|
|
body:
|
|
message: "Welcome to system1"
|
|
|
|
- method: GET
|
|
path: "/info"
|
|
response:
|
|
status: 200
|
|
body:
|
|
name: "System One"
|
|
version: "1.0"
|
|
```
|
|
|
|
### Placeholder Type Hints
|
|
|
|
You can include optional type hints in placeholders to ensure JSON values are rendered with correct types.
|
|
|
|
#### Supported types
|
|
|
|
| Type | Placeholder | Output |
|
|
|---------|------------------|-----------------|
|
|
| string | `{name}` | `"Alice"` |
|
|
| int | `{id:int}` | `42` *(number)* |
|
|
| float | `{price:float}` | `99.95` |
|
|
| boolean | `{enabled:bool}` | `true` |
|
|
|
|
If parsing fails (e.g. `abc` for an `int`), APImposter **falls back to the original string**.
|
|
|
|
### Relative Date/Time Placeholders
|
|
|
|
APImposter supports dynamic date and time placeholders that are calculated relative to the current time. This is perfect for testing calendar applications, scheduling systems, or any time-sensitive mock data.
|
|
|
|
#### Supported formats
|
|
|
|
| Format | Example | Description |
|
|
|--------|---------|-------------|
|
|
| `{datetime+/-offset}` | `{datetime+2d4h30m}` | DateTime with flexible time offset |
|
|
| `{datetime+/-Nd@HH:MM}` | `{datetime+1d@13:30}` | DateTime N days from now at specific time |
|
|
| `{date+/-Nd}` | `{date-7d}` | Date N days from now |
|
|
|
|
#### Format specifiers (optional)
|
|
|
|
Add a format specifier after a colon to control the output format:
|
|
|
|
| Specifier | Description | Example Output |
|
|
|-----------|-------------|----------------|
|
|
| `iso` (default) | ISO standard format | `2025-09-03T15:30:00` |
|
|
| `rfc3339` | RFC 3339 with UTC timezone | `2025-09-03T15:30:00Z` |
|
|
| Custom pattern | Any valid DateTimeFormatter pattern | `Sep 03, 2025` |
|
|
|
|
#### Examples
|
|
|
|
```yaml
|
|
# Basic relative dates (ISO format)
|
|
- method: GET
|
|
path: "/events"
|
|
response:
|
|
body:
|
|
todayMeeting: "{datetime+0d@08:00}" # Today at 8:00 AM
|
|
tomorrowLunch: "{datetime+1d@13:00}" # Tomorrow at 1:00 PM
|
|
nextWeek: "{date+7d}" # Date 7 days from now
|
|
pastEvent: "{datetime-2d4h30m}" # 2 days, 4h, 30m ago
|
|
yesterday: "{date-1d}" # Yesterday's date
|
|
|
|
# Different output formats
|
|
- method: GET
|
|
path: "/calendar"
|
|
response:
|
|
body:
|
|
# RFC3339 format
|
|
eventTimeRfc: "{datetime+1d@15:30:rfc3339}" # "2025-09-03T15:30:00Z"
|
|
|
|
# Custom formats
|
|
eventDate: "{date+1d:MMM dd, yyyy}" # "Sep 03, 2025"
|
|
timeOnly: "{datetime+0d@14:30:HH:mm}" # "14:30"
|
|
shortDate: "{date+7d:MM/dd/yy}" # "09/09/25"
|
|
```
|
|
|
|
#### Time components for offset format
|
|
|
|
- `d` = days
|
|
- `h` = hours
|
|
- `m` = minutes
|
|
|
|
All components are optional and can be combined: `{datetime+1d2h}`, `{datetime+30m}`, `{datetime-4h15m}`
|
|
|
|
**Note:** If a custom format is invalid, the system automatically falls back to ISO format to prevent errors.
|
|
|
|
---
|
|
|
|
## Example Requests and Responses
|
|
|
|
| YAML file | Request | Response Example |
|
|
|------------------------------------|----------------------------------|----------------------------------------------|
|
|
| `system1/users.yaml` | `GET /system1/users` | `{ "users": [{ "id": 1 }, { "id": 2 }] }` |
|
|
| `system1/users.yaml` | `GET /system1/users/42` | `{ "id": 42, "name": "User 42" }` |
|
|
| `system1/users.yaml` | `POST /system1/users` | `{ "message": "User created" }` |
|
|
| `system1/index.yaml` | `GET /system1` | `{ "message": "Welcome to system1" }` |
|
|
| `system1/index.yaml` | `GET /system1/info` | `{ "name": "System One", "version": "1.0" }` |
|
|
| `another-system/subdir/index.yaml` | `GET /another-system/subdir/...` | *(Defined response in file)* |
|
|
|
|
---
|
|
|
|
## Globals
|
|
|
|
Global values can be defined in a dedicated file `globals.yaml`:
|
|
|
|
```yaml
|
|
# mocks/globals.yaml
|
|
globals:
|
|
currentUserId: "user-123"
|
|
```
|
|
|
|
You can reference global values using `{globals.key}` or `{globals.key:type}` in responses.
|
|
|
|
---
|
|
|
|
## Conditional Responses
|
|
|
|
APImposter supports **conditional responses**, allowing different mock responses to be returned **based on request values** - query parameters, headers, path variables, or body fields.
|
|
|
|
This is useful for simulating realistic API behavior, such as authentication, filtering, or alternate success/error outcomes.
|
|
|
|
### Supported condition types:
|
|
|
|
- `path`: matches against path variables
|
|
- `query`: matches query parameters (`?filter=active`)
|
|
- `headers`: matches HTTP headers (case-insensitive)
|
|
- `body`: matches top-level JSON body fields (for POST/PUT)
|
|
|
|
### Example: conditionals by path
|
|
|
|
```yaml
|
|
- method: "GET"
|
|
path: "/users/{id}"
|
|
conditionalResponses:
|
|
- conditions:
|
|
path:
|
|
id: "1"
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
id: 1
|
|
name: "Lennart"
|
|
- conditions:
|
|
path:
|
|
id: "2"
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
id: 2
|
|
name: "Kurre"
|
|
response:
|
|
status: 404
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
error: "User not found"
|
|
```
|
|
|
|
### Example: conditionals by header
|
|
|
|
```yaml
|
|
- method: "GET"
|
|
path: "/secure"
|
|
conditionalResponses:
|
|
- conditions:
|
|
headers:
|
|
authorization: "Bearer abc123"
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
message: "Access granted"
|
|
response:
|
|
status: 401
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
error: "Unauthorized"
|
|
```
|
|
|
|
### Example: conditionals by query parameter
|
|
|
|
```yaml
|
|
- method: "GET"
|
|
path: "/products"
|
|
conditionalResponses:
|
|
- conditions:
|
|
query:
|
|
category: "books"
|
|
response:
|
|
status: 200
|
|
headers: { Content-Type: "application/json" }
|
|
body:
|
|
products:
|
|
- id: "b1"
|
|
name: "Data Structures and Algorithms in Java"
|
|
- conditions:
|
|
query:
|
|
category: "tech"
|
|
response:
|
|
status: 200
|
|
headers: { Content-Type: "application/json" }
|
|
body:
|
|
products:
|
|
- id: "t1"
|
|
name: "Keyboard"
|
|
response:
|
|
status: 200
|
|
headers: { Content-Type: "application/json" }
|
|
body:
|
|
products: []
|
|
|
|
```
|
|
|
|
### Example: conditionals by request body
|
|
|
|
```yaml
|
|
- method: "POST"
|
|
path: "/login"
|
|
conditionalResponses:
|
|
- conditions:
|
|
body:
|
|
username: "admin"
|
|
password: "secret"
|
|
response:
|
|
status: 200
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
token: "abc123"
|
|
response:
|
|
status: 403
|
|
headers:
|
|
Content-Type: application/json
|
|
body:
|
|
error: "Invalid credentials"
|
|
```
|
|
|
|
### Testing with `curl`
|
|
|
|
```bash
|
|
curl http://localhost:8080/mocks/users/1
|
|
|
|
curl -H "Authorization: Bearer abc123" http://localhost:8080/mocks/secure
|
|
|
|
curl "http://localhost:8080/mocks/products?category=books"
|
|
|
|
curl -X POST http://localhost:8080/mocks/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username": "admin", "password": "secret"}'
|
|
```
|
|
|
|
Conditions are evaluated **in order**, and the **first matching block wins**. If no condition matches, the fallback `response:` is used.
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
In `application.properties` you can configure the following properties:
|
|
|
|
- `mock.file-path`: The path to the directory containing your mock data YAML files.
|
|
- `mock.reload-interval-ms`: The interval in milliseconds at which the service checks for changes in the mock data files.
|
|
- `mock.base-path`: The base path for the mock service. This is prepended to all generated URLs. Default is `/mocks`.
|
|
- `mock.default-delay-ms`: The default delay in milliseconds for responses. This can be overridden in the YAML files.
|
|
|
|
---
|
|
|
|
## Swagger UI
|
|
|
|
OpenAPI Specification is generated from the mock data YAML files and can
|
|
be accessed via Swagger UI. You will find it at:
|
|
|
|
```
|
|
http://localhost:8080/swagger
|
|
```
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
Clone the project and run it simply with:
|
|
|
|
```shell
|
|
./mvnw spring-boot:run
|
|
```
|
|
|
|
or with Docker:
|
|
|
|
```shell
|
|
docker-compose up
|
|
```
|
|
|
|
---
|
|
|
|
🎭 **Good luck and happy mocking!** 🎭
|