Skip to content
Keploy × Go

Keploy + Go quickstart

Record, Replay, Relax: API Tests for Go with Keploy

Turn real API calls from a Gin + MongoDB app into tests and database mocks with Keploy.

  • ~25 min
  • Beginner
  • Windows + WSL
  • Keploy 3.8.57

What you'll learn

  • Record real API traffic as tests
    Wrap your normal start command and every request becomes a YAML test case.
  • Replay without a database
    Keploy answers MongoDB calls from recorded mocks, so tests pass with MongoDB switched off.
  • Catch a regression in seconds
    Change one status code and see Keploy point at exactly the endpoints that broke.
  • Avoid the gotchas I hit
    Login timeouts on WSL, an empty recording, the wrong MongoDB host, and a hidden paste character.

TL;DR

Already have WSL 2, Docker Desktop, Go and Keploy installed and logged in? This is the core flow:

Core flowbash
git clone --depth 1 https://github.com/keploy/samples-go.git && cd samples-go/gin-mongo
mv keploy keploy-original && sed -i 's/"mongoDb:27017"/"localhost:27017"/' main.go
docker compose up -d mongo
keploy record -c "go run main.go handler.go"   # Terminal 1; Ctrl+C when done
curl -X POST http://localhost:8080/url -H 'content-type: application/json' -d '{"url": "https://google.com"}'   # Terminal 2
keploy test -c "go run main.go handler.go" --delay 10

New to Keploy, or something broke? Follow the full steps below.

Why Keploy?

Testing a Gin handler that talks to MongoDB usually means writing httptest code plus hand-made mocks for the database. Those mocks are written from memory, so over time they drift from what MongoDB actually returns.

Keploy takes a different route. It sits next to your app, captures each API request and the database calls that request triggers, and saves both as YAML: test cases plus mocks. Later it replays the requests and answers the database calls from the mocks.

There's no SDK and no code change. You wrap the command you already use to start the app:

Bash
keploy record -c "go run main.go handler.go"

How it works

RECORD

You use the app normally; Keploy captures everything.

curlYour API request
Keploy proxyCaptures traffic
Gin appYour Go API
MongoDBReal database
keploy/test cases + mocks
TEST

Keploy replays the saved requests; no database required.

keploy/Saved test cases
KeploySends requests, checks responses
Gin appSame code, unchanged
Keploy mocksAnswers DB calls
MongoDBNot needed
Record once against the real database, then replay as many times as you like with Keploy standing in for MongoDB.

Prerequisites

This is the setup I tested on. Newer versions should work, but your output may differ slightly from mine.

ComponentVersion
OSWindows 11 + WSL 2 (Ubuntu 24.04, kernel 6.18)
DockerDocker Desktop 29.7.2 with WSL integration
Go1.27.1
Git2.43
Keploy3.8.57 (Free plan)
Sample appkeploy/samples-go → gin-mongo, a URL shortener built with Gin + MongoDB

Not on Windows? On Linux, skip Step 1 and run everything in your normal terminal. On macOS, follow Keploy's installation guide (opens in a new tab) for setup, then continue from Step 4.

Keploy needs a Linux kernel of 5.10 or newer. Check yours in Ubuntu (mine is 6.18):

Bash
uname -r
Do you need WSL?

Keploy's current install docs say it also runs natively on Windows (x86-64), without WSL. I used WSL anyway: the Linux path is the classic, best-documented route, and my MongoDB runs in Docker.

Step 1: Prepare WSL and Docker

Keploy and the Go app will run inside Ubuntu, and MongoDB will run in Docker. Both need to see each other, so set that up first.

  1. Check that Ubuntu runs on WSL 2

    In PowerShell:

    PowerShell
    wsl -l -v

    The VERSION column for your Ubuntu distro must say 2. WSL 1 doesn't run a real Linux kernel, and Keploy needs one to watch network traffic.

  2. Turn on Docker Desktop's WSL integration

    In Docker Desktop, open Settings → Resources → WSL integration, enable it for your Ubuntu distro, then click Apply & restart. This makes the docker command available inside Ubuntu.

  3. Test Docker from Ubuntu

    Bash
    docker run hello-world

    If this prints a hello message instead of "command not found", Docker works from WSL.

Paste with right-click

Pasting with Ctrl + V in the Ubuntu terminal inserted a hidden ^[[200~ in front of my command and broke it. Paste with right-click or Ctrl + Shift + V instead.

Step 2: Install Go

Ubuntu's apt offers Go 1.21, which is several releases behind. Install the official release from go.dev (opens in a new tab) instead:

Bash
GO_VERSION=go1.27.1
wget https://go.dev/dl/${GO_VERSION}.linux-amd64.tar.gz
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf ${GO_VERSION}.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.bashrc
source ~/.bashrc
go version

The rm -rf removes any older Go first, because extracting over an existing install can leave stale files behind. The PATH line adds the go binary and $HOME/go/bin, where go install puts tools.

go version
go version go1.27.1 linux/amd64

Step 3: Install Keploy and log in

Bash
curl --silent -O -L https://keploy.io/install.sh && source install.sh

My install printed v3.8.57, while the sample output in the docs shows 3.4.1. Don't worry if your output looks different from the docs.

On my machine, the installer also created a Docker network called keploy-network. The long hex ID it printed right after the progress bar is that network's ID (docker network ls shows it). You'll need this network in Step 5.

The installer also suggests keploy mcp-install, which sets Keploy up for AI coding agents. It's optional; skip it for this tutorial.

Next, log in so the CLI is linked to your Keploy account. The docs use the browser login; on WSL, the API key login is what worked for me:

Bash
keploy login
Browser login timed out on WSL

The sign-in link is valid for 1 minute. On WSL, no browser opened, and even after I signed in manually, the terminal timed out twice with "invalid or expired code".

What worked: create an API key at app.keploy.io (opens in a new tab) (profile → Account Settings → API Keys → Generate new token), then run keploy login --manual-login and paste the key.

When the key is accepted, you'll see:

keploy login --manual-login
API key verified successfully! You are on the Keploy Free plan.
Your API key is shown in plain text

The login prompt echoes the key, so never screenshot or share that terminal output. If the key leaks, delete the token at app.keploy.io (opens in a new tab) and create a new one.

Step 4: Get the sample app

Bash
git clone --depth 1 https://github.com/keploy/samples-go.git
cd samples-go/gin-mongo
go mod download

--depth 1 skips the repository's full history, which makes the clone much faster. go mod download fetches the dependencies now, so the first recorded run doesn't stall on downloads.

Simplified view of the folder
  • gin-mongo/
    • Dockerfile
    • docker-compose.yaml
    • go.mod
    • handler.go
    • main.go
    • keploy/pre-recorded by the repo

Before recording, two things need fixing.

Clear the old recordings

The repo ships an old keploy/ folder with someone else's recordings. Move it aside so your run starts clean:

Bash
mv keploy keploy-original

Point the app at localhost

Line 35 of main.go connects to mongoDb:27017. mongoDb is a container name, which only resolves inside Docker's network. In this tutorial, the Go app runs directly in Ubuntu, so it has to use localhost:27017 (Docker publishes MongoDB's port to the host).

Bash
sed -i 's/"mongoDb:27017"/"localhost:27017"/' main.go
main.go (line 35)diff
- client, err := New("mongoDb:27017", dbName)
+ client, err := New("localhost:27017", dbName)

The docs say this is on line 21; in the current repo it's line 35.

Step 5: Start MongoDB

Bash
docker compose up -d mongo
docker ps

In the docker ps output, the MongoDB container's PORTS column should show 0.0.0.0:27017->27017/tcp. That port mapping is what lets the app reach localhost:27017.

If Docker Compose warns that the attribute version is obsolete, ignore it. The warning is harmless.

Network not found?

docker-compose.yaml expects an external Docker network named keploy-network, which the Keploy installer created on my machine. If Docker Compose says the network is not found, create it and run docker compose up -d mongo again:

Bash
docker network create keploy-network

Step 6: Record test cases

You'll need two Ubuntu terminals, both in samples-go/gin-mongo.

  1. Terminal 1: Start the app under Keploy

    Bash
    keploy record -c "go run main.go handler.go"

    Keploy asks for your sudo password. It needs elevated rights to watch the app's network traffic.

    Wait before sending requests

    Wait for the line Started ingress forwarding {"orig_port": 8080, ...}. On my first attempt, no requests reached the app while recording, and Keploy saved an empty test set (testsRan: 0).

  2. Terminal 2: Send requests

    Shorten a URL:

    Bash
    curl --request POST \
      --url http://localhost:8080/url \
      --header 'content-type: application/json' \
      --data '{"url": "https://google.com"}'
    Response
    {"ts":1790866863265379939,"url":"http://localhost:8080/Lhr4BWAi"}

    The JSON has no trailing newline, so it sticks to your next prompt. The first time, I thought the request had printed nothing and re-sent it with curl -v to inspect it. That's why my recording has two POSTs for https://google.com (post-url-1 and post-url-2). Look for the JSON right before your prompt.

    The short code is derived from the URL, so https://google.com always gives Lhr4BWAi (the same code as in the docs), while ts changes every time.

    Now follow the short link. The app answers with a 303 redirect to https://google.com:

    Bash
    curl http://localhost:8080/Lhr4BWAi

    Repeat both calls with a second URL. For https://keploy.io, the response contained the short code 7fvpSsFg:

    Bash
    curl --request POST \
      --url http://localhost:8080/url \
      --header 'content-type: application/json' \
      --data '{"url": "https://keploy.io"}'
    curl http://localhost:8080/7fvpSsFg

    If you run exactly these commands, you'll get 4 tests; my run has 5 because of the re-sent POST, so expect 4/4 in Step 8 and 2 passed / 2 failed in Step 10.

  3. Terminal 1: Watch the captures

    Each request shows up as a [GIN] line followed by a capture message from Keploy:

    keploy record
    [GIN] 2026/10/01 - 14:58:44 | 200 |   21.646234ms |       127.0.0.1 | POST     "/url"
    🐰 Keploy: ... INFO 🟠 Keploy has captured test cases for the user's application. {"path": ".../keploy/test-set-0/tests", "testcase name": "post-url-1"}
    ...
    [GIN] 2026/10/01 - 15:02:26 | 303 |    3.874109ms |       127.0.0.1 | GET      "/Lhr4BWAi"
    🐰 Keploy: ... INFO 🟠 Keploy has captured test cases for the user's application. {"path": ".../keploy/test-set-0/tests", "testcase name": "get-lhr4bwai-1"}

    The test names are descriptive (post-url-1, get-lhr4bwai-1) instead of the docs' test-1.

Step 7: Stop recording (and meet auto-replay)

Press Ctrl + C in Terminal 1.

Keploy 3.8.57 doesn't stop right away. It automatically replays what it just recorded to find fields that change between runs (noise):

keploy record (after Ctrl+C)
🐰 Keploy: ... INFO auto-replay: recording stopped — now replaying what was just recorded to check for noise. Press Ctrl+C again to skip and exit. {"test_sets": 1, "opt_out": "--disableAutoReplay, or keploy.yml record.disableAutoReplay, to skip this on every run"}
...
🐰 Keploy: ... INFO persisted learned request-body noise onto mocks {"testSetID": "test-set-0", "mocksWithLearnedNoise": 3, "totalMocks": 10}
🐰 Keploy: ... INFO Successfully saved test-mock mappings {"testSetID": "test-set-0", "numTests": 5, "startupMocks": 0}

<=========================================>
TESTRUN SUMMARY. For test-set: "test-set-0"
      Total tests:        5
      Total test passed:  5
      Total test failed:  0
      Time Taken:         "15.07 s"
<=========================================>

All 5 recorded tests passed on replay. To skip this step, press Ctrl + C again, add --disableAutoReplay to the record command, or set record.disableAutoReplay in keploy.yml to skip it on every run.

Keploy generated this:

  • keploy/
    • reports/
      • test-run-0/
        • test-set-0-report.yaml
    • test-set-0/
      • config.yaml
      • mappings.yaml
      • mocks.yaml
      • tests/
        • get-7fvpssfg-1.yaml
        • get-lhr4bwai-1.yaml
        • post-url-1.yaml
        • post-url-2.yaml
        • post-url-3.yaml

tests/ holds one YAML file per request and mocks.yaml holds the recorded MongoDB traffic. I noticed that Keploy lowercased the path in the test name: /7fvpSsFg became get-7fvpssfg-1. Judging by the log line "Successfully saved test-mock mappings" (numTests: 5), mappings.yaml links each test case to the mocks it uses.

Here's a recorded test case, trimmed to the interesting parts:

keploy/test-set-0/tests/post-url-1.yamlyaml
version: api.keploy.io/v1beta1
kind: Http
name: post-url-1
spec:
  req:
    method: POST
    url: http://localhost:8080/url
    body: '{"url": "https://google.com"}'
  resp:
    status_code: 200
    body: '{"ts":1790866724945749450,"url":"http://localhost:8080/Lhr4BWAi"}'
  assertions:
    noise:
      body.ts: []
      header.Date: []

The highlighted noise block is the result of auto-replay. The docs tell you to add body.ts to noise by hand; in 3.8.57, Keploy detected body.ts and header.Date by itself. Each test file also contains a ready-to-run curl command that reproduces the request.

What to commit

Following Keploy's own guidance: commit tests/ and config.yaml, but not mocks.yaml. Mocks can get large, so Keploy gitignores mocks.yaml and uploads mocks to its registry instead.

Step 8: Run the tests

In Terminal 1 (still in samples-go/gin-mongo), run the tests. MongoDB is still running for now:

Bash
keploy test -c "go run main.go handler.go" --delay 10

--delay 10 gives the app 10 seconds to boot before Keploy starts replaying requests. Without it, the first requests could hit an app that isn't listening yet.

5 recorded API calls, 5 passing tests, zero lines of test code.

Step 9: Prove the mocks work by turning MongoDB off

If the tests really use the mocks, they shouldn't care whether MongoDB is running. Stop it and test again:

Bash
docker compose stop mongo
keploy test -c "go run main.go handler.go" --delay 10

All 5 tests still pass, and Keploy's summary calls the suite "dependency-free".

In test mode, the app believes it's talking to MongoDB, but Keploy answers with the recorded responses. That means these tests can run in CI without spinning up a database.

Start MongoDB again before you continue:

Bash
docker compose start mongo

Step 10: Catch a real regression

Imagine a teammate "improves" the redirect from 303 See Other to 301 Moved Permanently. It compiles and looks harmless. But browsers cache 301 redirects, so if a short link is ever changed, users keep landing on the old URL.

The redirect is on line 66 of handler.go. Introduce the change:

Bash
sed -i '66s/http.StatusSeeOther/http.StatusMovedPermanently/' handler.go
keploy test -c "go run main.go handler.go" --delay 10
handler.go (line 66)diff
- c.Redirect(http.StatusSeeOther, u.URL)
+ c.Redirect(http.StatusMovedPermanently, u.URL)

Keploy flags the difference:

The recorded response was a 303; the changed code now returns a 301.
Only the two redirect tests failed.

Only the two GET (redirect) tests failed; the three POST tests passed. Keploy points at exactly the endpoints whose behavior changed.

Keploy's output also warns that noise was already calibrated at record time, so these are real differences. Don't hide them by marking them as noise. A failing test is information, not something to silence.

Revert the change and run the tests again to get back to 5/5:

Bash
git checkout handler.go
keploy test -c "go run main.go handler.go" --delay 10

Clean up

When you're done, stop and remove the MongoDB container (run this in samples-go/gin-mongo):

Bash
docker compose down

The keploy/ folder with your tests stays on disk, so you can run keploy test again later, after docker compose up -d mongo or even without MongoDB.

My takeaways

What surprised me most

Stopping MongoDB and still seeing 5 out of 5 tests pass. That's when it clicked for me: the tests no longer depend on a real database, which is exactly what makes running them in CI simple.

What was hardest

Not Keploy itself, but the environment around it. The browser login timed out on WSL until I switched to an API key, and my first recording saved zero tests because I sent requests before Keploy was ready. The lesson: read the logs, and wait for Started ingress forwarding before sending traffic.

Would I use it?

Yes, for API-heavy Go services, as a regression safety net next to normal unit tests. Recording real traffic took minutes, and it caught a one-word bug (303 to 301) that would easily pass code review. I'd still review what gets marked as noise and keep mocks.yaml out of Git, as Keploy recommends.

What's different in Keploy 3.8.57

If you follow the official quickstart alongside this guide, here's where my run with Keploy 3.8.57 differed:

TopicQuickstart docsWhat I saw
Version in sample output3.4.13.8.57
LoginBrowser loginBrowser login timed out on WSL; --manual-login with an API key worked
MongoDB address in main.goLine 21Line 35
Generated filestest-1.yml + mocks.ymlconfig.yaml, mappings.yaml, mocks.yaml, and tests/ with descriptive names
NoiseAdd body.ts manuallyDetected automatically by auto-replay
After recordingNothing mentionedAutomatic replay plus a link to a cloud test report

Troubleshooting

Where to go next

Useful links: