Introduction

Keploy is an open-source API Testing tool developed in Go that can record API request logs during local development and generate test cases. The generated test cases include request and response parameters as well as Mock configurations, allowing you to easily integrate these cases into your existing CI/CD Pipeline, thereby further improving code test coverage in CI/CD!

Supported languages:

  • Go
  • C#
  • Rust
  • Java
  • Python
  • JavaScript

Backend data dependencies supported for mocking:

  • HTTP
  • MySQL
  • Redis
  • MongoDB
  • DynamoDB
  • PostgreSQL

Architecture

Architecture

Keploy has two operating modes:

  • Record mode: Keploy injects eBPF hooks to capture incoming HTTP traffic and redirects outgoing TCP/UDP traffic to its proxy server. The proxy server asynchronously captures packets and saves them in YAML files.
  • Test mode: Keploy reads YAML files for test cases and stubs/mocks. It starts the application, sends recorded HTTP test cases, and simulates responses for outgoing calls. This ensures no side effects due to non-idempotency.

Installation

Since Keploy uses eBPF under the hood, it can only be used on Linux-based environments, while Windows is not supported and can only run Keploy through WSL. Although macOS has supported eBPF since Big Sur (11.0), the support is limited. Apple implemented a restricted version of BPF on macOS (called Restricted BPF, or rbpf), which is not fully compatible with Linux’s eBPF. Therefore, on macOS, Keploy runs in Docker.

For specific installation methods, please refer to the official documentation.

Experiment

Clone the official example project:

docker network create keploy-network

git clone https://github.com/keploy/samples-go.git && cd samples-go/echo-sql

Cloning into 'samples-go'...
remote: Enumerating objects: 2068, done.
remote: Counting objects: 100% (1540/1540), done.
remote: Compressing objects: 100% (769/769), done.
remote: Total 2068 (delta 838), reused 1210 (delta 664), pack-reused 528 (from 1)
Receiving objects: 100% (2068/2068), 120.09 MiB | 15.36 MiB/s, done.
Resolving deltas: 100% (1004/1004), done.

go mod download
docker build -t echo-app:1.0 .

Modify the docker-compose.yaml file configuration as follows:

services:
  postgres:
    image: postgres:10.5
    container_name: postgresDb
    restart: always
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    ports:
      - '5432:5432'
    volumes: 
      # - ./postgres-data:/var/lib/postgresql/data
      # copy the sql script to create tables
      - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql
    networks:
      - keploy-network

networks:
  keploy-network:
    name: keploy-network
    external: true

Start the service:

docker compose up

Run Keploy:

keploy record -c "docker run -p 8082:8082 --name echoSqlApp --network keploy-network echo-app:1.0"

       ▓██▓▄
    ▓▓▓▓██▓█▓▄
     ████████▓▒
          ▀▓▓███▄      ▄▄   ▄               ▌
         ▄▌▌▓▓████▄    ██ ▓█▀  ▄▌▀▄  ▓▓▌▄   ▓█  ▄▌▓▓▌▄ ▌▌   ▓
       ▓█████████▌▓▓   ██▓█▄  ▓█▄▓▓ ▐█▌  ██ ▓█  █▌  ██  █▌ █▓
      ▓▓▓▓▀▀▀▀▓▓▓▓▓▓▌  ██  █▓  ▓▌▄▄ ▐█▓▄▓█▀ █▓█ ▀█▄▄█▀   █▓█
       ▓▌                           ▐█▌                   █▌
        ▓

version: 2.3.0-beta31

🐰 Keploy: 2024-10-26T08:08:06Z 	INFO	detected that Keploy is running in a docker container
🐰 Keploy: 2024-10-26T08:08:06Z 	WARN	buildDelay is set to 30, incase your docker container takes more time to build use --buildDelay to set custom delay
🐰 Keploy: 2024-10-26T08:08:06Z 	INFO	Example usage: keploy record -c "docker-compose up --build" --buildDelay 35
🐰 Keploy: 2024-10-26T08:08:06Z 	INFO	trying to inject network:keploy-network to the keploy container
🐰 Keploy: 2024-10-26T08:08:06Z 	INFO	Successfully injected network to the keploy container	{"Keploy container": "keploy-v2", "appNetwork": "keploy-network", "keploy container ip": "192.168.107.3"}
🐰 Keploy: 2024-10-26T08:08:07Z 	INFO	keploy initialized and probes added to the kernel.
🐰 Keploy: 2024-10-26T08:08:07Z 	INFO	starting UDP DNS server at addr :26789
🐰 Keploy: 2024-10-26T08:08:07Z 	INFO	starting TCP DNS server at addr :26789
🐰 Keploy: 2024-10-26T08:08:07Z 	INFO	Keploy has taken control of the DNS resolution mechanism, your application may misbehave if you have provided wrong domain name in your application code.
🐰 Keploy: 2024-10-26T08:08:07Z 	INFO	Proxy started at port:16789

   ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.9.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8082

After running, both Keploy and the service are started. Now you can send requests to the service:

curl --request POST \
  --url http://localhost:8082/url \
  --header 'content-type: application/json' \
  --data '{"url": "https://github.com"}'

{"ts":1729930100440833849,"url":"http://localhost:8082/4KepjkTT"}

curl --request GET \
  --url http://localhost:8082/4KepjkTT

When the request is sent, the following logs will be output in the Keploy console:

🐰 Keploy: 2024-10-26T08:08:22Z 	INFO	🟠 Keploy has captured test cases for the user's application.	{"path": "/Users/George/Develop/samples-go/echo-sql/keploy/test-set-1/tests", "testcase name": "test-1"}
🐰 Keploy: 2024-10-26T08:09:03Z 	INFO	🟠 Keploy has captured test cases for the user's application.	{"path": "/Users/George/Develop/samples-go/echo-sql/keploy/test-set-1/tests", "testcase name": "test-3"}

At this point, the requests have been successfully intercepted, and corresponding request record YAML files have been generated. Next, you can check the generated file contents in the project root directory.

First POST request:

version: api.keploy.io/v1beta1
kind: Http
name: test-1
spec:
    metadata: {}
    req:
        method: POST
        proto_major: 1
        proto_minor: 1
        url: http://localhost:8082/url
        header:
            Accept: '*/*'
            Content-Length: "33"
            Content-Type: application/json
            Host: localhost:8082
            User-Agent: curl/8.7.1
        body: |-
            {
              "url": "https://github.com"
            }
        timestamp: 2024-10-26T08:08:20.436143805Z
    resp:
        status_code: 200
        header:
            Content-Length: "66"
            Content-Type: application/json; charset=UTF-8
            Date: Sat, 26 Oct 2024 08:08:20 GMT
        body: |
            {"ts":1729930100440833849,"url":"http://localhost:8082/4KepjkTT"}
        status_message: OK
        proto_major: 0
        proto_minor: 0
        timestamp: 2024-10-26T08:08:22.510298785Z
    objects: []
    assertions:
        noise:
            body.ts: []
            header.Date: []
    created: 1729930102
curl: |-
    curl --request POST \
      --url http://localhost:8082/url \
      --header 'Host: localhost:8082' \
      --header 'User-Agent: curl/8.7.1' \
      --header 'Accept: */*' \
      --header 'Content-Type: application/json' \
      --data "{\n  \"url\": \"https://github.com\"\n}"

Second GET request:

version: api.keploy.io/v1beta1
kind: Http
name: test-3
spec:
    metadata: {}
    req:
        method: GET
        proto_major: 1
        proto_minor: 1
        url: http://localhost:8082/4KepjkTT
        header:
            Accept: '*/*'
            Host: localhost:8082
            User-Agent: curl/8.7.1
        body: ""
        timestamp: 2024-10-26T08:09:01.286595576Z
    resp:
        status_code: 308
        header:
            Content-Length: "0"
            Date: Sat, 26 Oct 2024 08:09:01 GMT
            Location: https://github.com
        body: ""
        status_message: Permanent Redirect
        proto_major: 0
        proto_minor: 0
        timestamp: 2024-10-26T08:09:03.368781888Z
    objects: []
    assertions:
        noise:
            header.Date: []
    created: 1729930143
curl: |
    curl --request GET \
      --url http://localhost:8082/4KepjkTT \
      --header 'Accept: */*' \
      --header 'Host: localhost:8082' \
      --header 'User-Agent: curl/8.7.1' \

mocks.yaml file:

version: api.keploy.io/v1beta1
kind: Postgres
name: mock-0
spec:
    metadata:
        type: config
    postgresrequests:
        - identifier: StartupRequest
          length: 102
          payload: AAAAZgADAAB1c2VyAHBvc3RncmVzAGRhdGFiYXNlAHBvc3RncmVzAGRhdGVzdHlsZQBJU08sIE1EWQBjbGllbnRfZW5jb2RpbmcAVVRGOABleHRyYV9mbG9hdF9kaWdpdHMAMgAA
          startup_message:
            protocolversion: 196608
            parameters:
                client_encoding: UTF8
                database: postgres
                datestyle: ISO, MDY
                extra_float_digits: "2"
                user: postgres
          auth_type: 0
    postgresresponses:
        - header: [R]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [145, 212, 36, 22]
          msg_type: 82
          auth_type: 5
    reqtimestampmock: 2024-10-26T08:08:09.723623968Z
    restimestampmock: 2024-10-26T08:08:09.725945385Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-1
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [p]
          identifier: ClientRequest
          length: 102
          password_message:
            password: md56527563748c9648a3030941e6094c992
          msg_type: 112
          auth_type: 0
    postgresresponses:
        - header: [R, S, S, S, S, S, S, S, S, S, S, S, K, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          backend_key_data:
            process_id: 68
            secret_key: 3063486908
          parameter_status:
            - name: application_name
              value: ""
            - name: client_encoding
              value: UTF8
            - name: DateStyle
              value: ISO, MDY
            - name: integer_datetimes
              value: "on"
            - name: IntervalStyle
              value: postgres
            - name: is_superuser
              value: "on"
            - name: server_encoding
              value: UTF8
            - name: server_version
              value: 10.5 (Debian 10.5-2.pgdg90+1)
            - name: session_authorization
              value: postgres
            - name: standard_conforming_strings
              value: "on"
            - name: TimeZone
              value: UTC
            - name: TimeZone
              value: UTC
            - name: TimeZone
              value: UTC
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:09.734120389Z
    restimestampmock: 2024-10-26T08:08:09.734230184Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-2
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [Q]
          identifier: ClientRequest
          length: 102
          query:
            string: ;
          msg_type: 81
          auth_type: 0
    postgresresponses:
        - header: [I, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:09.734854874Z
    restimestampmock: 2024-10-26T08:08:09.734906876Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-3
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [Q]
          identifier: ClientRequest
          length: 102
          payload: UQAAAMsKCQlDUkVBVEUgVEFCTEUgSUYgTk9UIEVYSVNUUyB1cmxfbWFwICgKCQkJaWQgICAgICAgICAgIFZBUkNIQVIoMjU1KSBQUklNQVJZIEtFWSwKCQkJcmVkaXJlY3RfdXJsIFZBUkNIQVIoMjU1KSBOT1QgTlVMTCwKCQkJY3JlYXRlZF9hdCAgIFRJTUVTVEFNUCBOT1QgTlVMTCwKCQkJdXBkYXRlZF9hdCAgIFRJTUVTVEFNUCBOT1QgTlVMTAoJCSk7CgkA
          query:
            string: ' CREATE TABLE IF NOT EXISTS url_map ( id VARCHAR(255) PRIMARY KEY, redirect_url VARCHAR(255) NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); '
          msg_type: 81
          auth_type: 0
    postgresresponses:
        - header: ["N", C, Z]
          identifier: ServerResponse
          length: 102
          payload: TgAAAHRTTk9USUNFAFZOT1RJQ0UAQzQyUDA3AE1yZWxhdGlvbiAidXJsX21hcCIgYWxyZWFkeSBleGlzdHMsIHNraXBwaW5nAEZwYXJzZV91dGlsY21kLmMATDIwOQBSdHJhbnNmb3JtQ3JlYXRlU3RtdAAAQwAAABFDUkVBVEUgVEFCTEUAWgAAAAVJ
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          command_complete:
            - command_tag_type: CREATE TABLE
          notice_response:
            severity: NOTICE
            severity_unlocalized: NOTICE
            code: 42P07
            message: relation "url_map" already exists, skipping
            detail: ""
            hint: ""
            position: 0
            internal_position: 0
            internal_query: ""
            where: ""
            schema_name: ""
            table_name: ""
            column_name: ""
            data_type_name: ""
            constraint_name: ""
            file: parse_utilcmd.c
            line: 209
            routine: transformCreateStmt
            unknown_fields: {}
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:09.737489386Z
    restimestampmock: 2024-10-26T08:08:09.737585347Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-4
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [Q]
          identifier: ClientRequest
          length: 102
          query:
            string: ;
          msg_type: 81
          auth_type: 0
    postgresresponses:
        - header: [I, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:20.440583674Z
    restimestampmock: 2024-10-26T08:08:20.44066376Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-5
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [P, D]
          identifier: ClientRequest
          length: 102
          payload: UAAAADcACgkJCVNFTEVDVCAqCgkJCUZST00gdXJsX21hcAoJCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
          describe:
            object_type: 83
            name: ""
          parse:
            - name: ""
              query: ' SELECT * FROM url_map WHERE id = $1 '
              parameter_oids: []
          msg_type: 68
          auth_type: 0
    postgresresponses:
        - header: ["1", t, T, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          parameter_description:
            parameteroids:
                - 1042
          ready_for_query:
            txstatus: 73
          row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:20.441889138Z
    restimestampmock: 2024-10-26T08:08:20.442930967Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-6
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [B, E]
          identifier: ClientRequest
          length: 102
          payload: QgAAABgAAAAAAAEAAAAINEtlcGprVFQAAEUAAAAJAAAAAABTAAAABA==
          bind:
            - parameters: [[52, 75, 101, 112, 106, 107, 84, 84]]
          execute:
            - {}
          msg_type: 69
          auth_type: 0
    postgresresponses:
        - header: ["2", C, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          command_complete:
            - command_tag_type: SELECT 0
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:20.444335976Z
    restimestampmock: 2024-10-26T08:08:20.444560068Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-7
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [P, D]
          identifier: ClientRequest
          length: 102
          payload: UAAAAGUACgkJSU5TRVJUIElOVE8gdXJsX21hcCAoaWQsIHJlZGlyZWN0X3VybCwgY3JlYXRlZF9hdCwgdXBkYXRlZF9hdCkKCQlWQUxVRVMgKCQxLCAkMiwgJDMsICQ0KQoJAAAARAAAAAZTAFMAAAAE
          describe:
            object_type: 83
            name: ""
          parse:
            - name: ""
              query: ' INSERT INTO url_map (id, redirect_url, created_at, updated_at) VALUES ($1, $2, $3, $4) '
              parameter_oids: []
          msg_type: 68
          auth_type: 0
    postgresresponses:
        - header: ["1", t, "n", Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          parameter_description:
            parameteroids:
                - 1042
                - 1043
                - 1114
                - 1114
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:20.445084295Z
    restimestampmock: 2024-10-26T08:08:20.445454558Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-8
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [B, E]
          identifier: ClientRequest
          length: 102
          payload: QgAAAHIAAAAAAAQAAAAINEtlcGprVFQAAAASaHR0cHM6Ly9naXRodWIuY29tAAAAHjIwMjQtMTAtMjYgMDg6MDg6MjAuNDQwODMzODQ5WgAAAB4yMDI0LTEwLTI2IDA4OjA4OjIwLjQ0MDgzMzg0OVoAAEUAAAAJAAAAAABTAAAABA==
          bind:
            - parameters: [[52, 75, 101, 112, 106, 107, 84, 84], [104, 116, 116, 112, 115, 58, 47, 47, 103, 105, 116, 104, 117, 98, 46, 99, 111, 109], [50, 48, 50, 52, 45, 49, 48, 45, 50, 54, 32, 48, 56, 58, 48, 56, 58, 50, 48, 46, 52, 52, 48, 56, 51, 51, 56, 52, 57, 90], [50, 48, 50, 52, 45, 49, 48, 45, 50, 54, 32, 48, 56, 58, 48, 56, 58, 50, 48, 46, 52, 52, 48, 56, 51, 51, 56, 52, 57, 90]]
          execute:
            - {}
          msg_type: 69
          auth_type: 0
    postgresresponses:
        - header: ["2", C, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          command_complete:
            - command_tag_type: INSERT 0 1
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:20.451974127Z
    restimestampmock: 2024-10-26T08:08:20.451997753Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-9
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [Q]
          identifier: ClientRequest
          length: 102
          query:
            string: ;
          msg_type: 81
          auth_type: 0
    postgresresponses:
        - header: [I, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:45.584922216Z
    restimestampmock: 2024-10-26T08:08:45.584979593Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-10
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [P, D]
          identifier: ClientRequest
          length: 102
          payload: UAAAADQACgkJU0VMRUNUICoKCQlGUk9NIHVybF9tYXAKCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
          describe:
            object_type: 83
            name: ""
          parse:
            - name: ""
              query: ' SELECT * FROM url_map WHERE id = $1 '
              parameter_oids: []
          msg_type: 68
          auth_type: 0
    postgresresponses:
        - header: ["1", t, T, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          parameter_description:
            parameteroids:
                - 1042
          ready_for_query:
            txstatus: 73
          row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:45.585466111Z
    restimestampmock: 2024-10-26T08:08:45.585555489Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-11
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [B, E]
          identifier: ClientRequest
          length: 102
          payload: QgAAABgAAAAAAAEAAAAIR3V3SENnb1EAAEUAAAAJAAAAAABTAAAABA==
          bind:
            - parameters: [[71, 117, 119, 72, 67, 103, 111, 81]]
          execute:
            - {}
          msg_type: 69
          auth_type: 0
    postgresresponses:
        - header: ["2", C, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          command_complete:
            - command_tag_type: SELECT 0
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:08:45.588724562Z
    restimestampmock: 2024-10-26T08:08:45.588848191Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-12
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [Q]
          identifier: ClientRequest
          length: 102
          query:
            string: ;
          msg_type: 81
          auth_type: 0
    postgresresponses:
        - header: [I, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          ready_for_query:
            txstatus: 73
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:09:01.287327019Z
    restimestampmock: 2024-10-26T08:09:01.287367771Z
connectionId: "0"
---
version: api.keploy.io/v1beta1
kind: Postgres
name: mock-13
spec:
    metadata:
        type: config
    postgresrequests:
        - header: [P, D]
          identifier: ClientRequest
          length: 102
          payload: UAAAADQACgkJU0VMRUNUICoKCQlGUk9NIHVybF9tYXAKCQlXSEVSRSBpZCA9ICQxCgkAAABEAAAABlMAUwAAAAQ=
          describe:
            object_type: 83
            name: ""
          parse:
            - name: ""
              query: ' SELECT * FROM url_map WHERE id = $1 '
              parameter_oids: []
          msg_type: 68
          auth_type: 0
    postgresresponses:
        - header: ["1", t, T, Z]
          identifier: ServerResponse
          length: 102
          authentication_md5_password:
            salt: [0, 0, 0, 0]
          parameter_description:
            parameteroids:
                - 1042
          ready_for_query:
            txstatus: 73
          row_description: {fields: [{field_name: id, table_oid: 16384, table_attribute_number: 1, data_type_oid: 1042, data_type_size: -1, type_modifier: 12, format: 0}, {field_name: redirect_url, table_oid: 16384, table_attribute_number: 2, data_type_oid: 1043, data_type_size: -1, type_modifier: 154, format: 0}, {field_name: created_at, table_oid: 16384, table_attribute_number: 3, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}, {field_name: updated_at, table_oid: 16384, table_attribute_number: 4, data_type_oid: 1114, data_type_size: 8, type_modifier: -1, format: 0}]}
          msg_type: 90
          auth_type: 0
    reqtimestampmock: 2024-10-26T08:09:01.287893998Z
    restimestampmock: 2024-10-26T08:09:01.287930416Z
connectionId: "0"

You can see that the mocks.yaml file also records the SQL executed in PostgreSQL, which is what was mentioned above about ensuring no side effects due to non-idempotency in test mode.

CI/CD Integration

.gitlab-ci.yml:

---
stages:
  - test

keploy-test-job: # This job runs in the test stage.
  image: ubuntu:latest
  stage: test
  before_script:
    ## Add the dependencies && Install Keploy Binary

    - apt update && apt install -y sudo curl
    - curl --silent --location "https://github.com/keploy/keploy/releases/latest/download/keploy_linux_amd64.tar.gz" | tar xz --overwrite -C /tmp
    - sudo mkdir -p /usr/local/bin && sudo mv /tmp/keploy /usr/local/bin/keploy
    - sudo mount -t debugfs debugfs /sys/kernel/debug

  script:
    ## Steps to run application
    ...

In the testing phase of CI/CD, we can use the previously recorded request records and mocks configurations to replay requests!

Conclusion

Currently, some API Docs management tools I know, such as Postman and Apifox, have a certain cost to integrate into existing CI/CD Pipelines. They cannot achieve true self-hosting, while Keploy can achieve true self-hosting and minimize test side effects to the greatest extent!

The power of this tool lies in its ability to analyze the dependencies of backend services, such as API requests to upstream services, without requiring code or complex configurations. This is something that cannot be achieved in traditional development experiences and can only be done manually by developers!

I hope this is helpful, Happy hacking…