How to Mock a RESTful Microservice
Tags: webdev, microservices, mocking, rest
Published: 2020-12-30 23:33:42

👋 there!

Recently I created a simple API mocking service called Rhino. That was because sometimes i have to integrate with a RESTful web service which is not ready yet or requires a lot of effort to setup and fill with data or Need a backend for a single page application & backend not ready yet.

{% github clivern/rhino %}

Rhino enables easy mocking of any HTTP web service for testing and debugging purposes. Also it can simulate high latencies and failures to make sure your services have the capability to withstand and recover from failures. It supports cross-origin resource sharing (CORS) so it can be used as a backend for single page applications.

#### In order to run Rhino locally:
* You will have to download one of the binaries from this page https://github.com/Clivern/Rhino/releases and make it globally executable.
* Create a config file
```
{
    "app": {
        "mode": "prod",
        "port": "8080",
        "domain": "http://127.0.0.1:8080",
        "tls": {
            "status": "off",
            "pemPath": "/cert/server.pem",
            "keyPath": "/cert/server.key"
        }
    },
    "mock": [
        {
            "path": "/api/v2/service1/item",
            "request": {
                "method": "get"
            },
            "response": {
                "statusCode": 200,
                "headers": [
                    {"key": "Content-Type", "value": "application/json"}
                ],
                "body": "{\"action\": \"list\"}"
            },
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        },
        {
            "path": "/api/v2/service1/item",
            "request": {
                "method": "post"
            },
            "response": {
                "statusCode": 200,
                "headers": [
                    {"key": "Content-Type", "value": "application/json"}
                ],
                "body": "{\"action\": \"create\"}"
            },
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        },
        {
            "path": "/api/v2/service1/item/:id",
            "request": {
                "method": "get"
            },
            "response": {
                "statusCode": 200,
                "headers": [
                    {"key": "Content-Type", "value": "application/json"}
                ],
                "body": "{\"id\": \":id\", \"action\": \"get one\"}"
            },
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        },
        {
            "path": "/api/v2/service1/item/:id",
            "request": {
                "method": "put"
            },
            "response": {
                "statusCode": 200,
                "headers": [
                    {"key": "Content-Type", "value": "application/json"}
                ],
                "body": "{\"id\": \":id\", \"action\": \"update\"}"
            },
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        },
        {
            "path": "/api/v2/service1/item/:id",
            "request": {
                "method": "delete"
            },
            "response": {
                "statusCode": 200,
                "headers": [
                    {"key": "Content-Type", "value": "application/json"}
                ],
                "body": "{\"id\": \":id\", \"action\": \"delete\"}"
            },
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        }
    ],
    "debug": [
        {
            "path": "/api/debug",
            "chaos": {
                "latency": "0s",
                "failRate": "0%"
            }
        }
    ],
    "log": {
        "level": "info",
        "output": "stdout",
        "format": "json"
    }
}
```
* The run rhino with that config file

```zsh
$ rhino serve -c config.json
```

* On a different terminal, let's call these mocked endpoints

```zsh
➜  ~ curl -X POST "http://127.0.0.1:8080/api/v2/service1/item"
{"action": "create"}

➜  ~ curl -X GET "http://127.0.0.1:8080/api/v2/service1/item"
{"action": "list"}

➜  ~ curl -X GET "http://127.0.0.1:8080/api/v2/service1/item/1"
{"id": "1", "action": "get one"}

➜  ~ curl -X PUT "http://127.0.0.1:8080/api/v2/service1/item/1"
{"id": "1", "action": "update"}

➜  ~ curl -X DELETE "http://127.0.0.1:8080/api/v2/service1/item/1"
{"id": "1", "action": "delete"}

```

#### To run rhino with docker and docker compose:

Just close the repository and use either the simple or the advanced setup.

```zsh
# Simple setup
$ git clone https://github.com/Clivern/Rhino.git
$ cd Rhino/deployment/basic/docker-compose
$ docker-compose up -d

# In case you want to visualize incoming requests with grafana
$ git clone https://github.com/Clivern/Rhino.git
$ cd Rhino/deployment/advanced/docker-compose
$ docker-compose up -d
```

Rhino supports a bunch of fake data flags that can be used in the endpoint definition

```zsh
AnyOf: @fake(:anyof[A||B||C||D])
Latitude: @fake(:lat)
Longitude: @fake(:long)
CreditCardNumber: @fake(:cc_number)
CreditCardType: @fake(:cc_type)
Email: @fake(:email)
DomainName: @fake(:domain_name)
IPV4: @fake(:ipv4)
IPV6: @fake(:ipv6)
Password: @fake(:password)
PhoneNumber: @fake(:phone_number)
MacAddress: @fake(:mac_address)
URL: @fake(:url)
UserName: @fake(:username)
TollFreeNumber: @fake(:toll_free_number)
E164PhoneNumber: @fake(:e_164_phone_number)
TitleMale: @fake(:title_male)
TitleFemale: @fake(:title_female)
FirstName: @fake(:first_name)
FirstNameMale: @fake(:first_name_male)
FirstNameFemale: @fake(:first_name_female)
LastName: @fake(:last_name)
Name: @fake(:name)
UnixTime: @fake(:unix_time)
Date: @fake(:date)
Time: @fake(:time)
MonthName: @fake(:month_name)
Year: @fake(:year)
DayOfWeek: @fake(:day_of_week)
DayOfMonth: @fake(:day_of_month)
Timestamp: @fake(:timestamp)
Century: @fake(:century)
TimeZone: @fake(:timezone)
TimePeriod: @fake(:time_period)
Word: @fake(:word)
Sentence: @fake(:sentence)
Paragraph: @fake(:paragraph)
Currency: @fake(:currency)
Amount: @fake(:amount)
AmountWithCurrency: @fake(:amount_with_currency)
UUIDHypenated: @fake(:uuid_hyphenated)
UUID: @fake(:uuid_digit)
```

Happy coding everyone! ❤️