Record and replay Go API tests with Keploy
A hands-on walkthrough of Keploy's Gin and MongoDB quickstart. Record real API calls, then replay them with the database switched off.
- By
- Priyanshu Jha
- Published
- 2 October 2026
- Keploy
- 3.8.58 (Free)
- Go
- 1.27.1
- OS
- macOS, Apple Silicon
What you will build#
You will take a small Go URL shortener, record real HTTP calls against it with Keploy, and then replay those calls as automated tests. The shortener uses Gin for the API and MongoDB for storage.
Then you will stop MongoDB and run the tests again. They still pass, because Keploy recorded the database conversation too and plays it back.
Record
keploy recordYou
send requests with curl
Keploy proxy
watches the traffic
Go app
handles the request
MongoDB
real database
Saved to keploy/: test cases and mocks as YAML
Replay
keploy testKeploy
sends saved requests
Go app
handles the request
Keploy mocks
answer for MongoDB
Responses compared with the saved ones: pass or fail
I followed the "Running App Locally" variant of the Gin + MongoDB quickstart, which runs the app on the host and MongoDB in Docker.
What is Keploy#
Keploy is an open-source tool that writes API tests for you by watching real traffic. You start your app through Keploy, send requests to it, and Keploy saves each request and response as a test case. It also saves what your app said to its dependencies, such as a database, as mocks.
Later you run keploy test. Keploy sends the saved requests to your app and compares the new responses with the saved ones. Dependency calls are answered from the mocks, so the database does not need to be running.
Two terms come up a lot from here on:
- Test case: one recorded HTTP request and the response it got.
- Mock: one recorded exchange between your app and a dependency. Here that means MongoDB.
Prerequisites#
You need these installed before you start:
- Go. I installed it with
brew install go. - Docker Desktop, running. It only hosts MongoDB in this setup.
- git and curl.
- A free Keploy account. The first
keploy recordopens a browser window to sign in. More on that later.
Check the basics:
go version && docker --version && git --versionGet the sample app#
Clone the samples repository
The sample lives in the gin-mongo folder of Keploy's samples-go repository.
git clone https://github.com/keploy/samples-go.git && cd samples-go/gin-mongoDownload the Go dependencies
go mod downloadLook at the API
The app exposes three routes, registered in main.go:
r.GET("/:param", getURL)
r.POST("/url", putURL)
r.GET("/verify-email", verifyEmail)POST /url takes a long URL, stores it in MongoDB under a short hash and returns the short link. GET /:param looks the hash up and redirects to the original URL. I only used these two routes.
Prepare the app#
The sample is set up to run inside Docker Compose, so its MongoDB host is a Compose service name. We run the app on the host instead, so it needs to point at localhost.
Point the app at localhost
In main.go, change the MongoDB address passed to New:
client, err := New("localhost:27017", dbName)It was "mongoDb:27017" before. The quickstart says to edit line 21. In the version I cloned the line is 35, so search for mongoDb rather than trusting the line number.
Create the Docker network
The Compose file declares a network called keploy-network as external, which means Compose will not create it for you. I created it first.
docker network create keploy-networkStart MongoDB
The -d flag runs it in the background so you keep the terminal.
docker compose up mongo -dBuild the app into a binary
The quickstart recommends running a built binary on macOS instead of go run. I followed that advice and never tried go run.
go build -o test-app-url-shortener .Install Keploy#
The installer script downloads the Keploy CLI for your system.
curl --silent -O -L https://keploy.io/install.sh && source install.shThen confirm it works:
keploy --versionI got Keploy 3.8.58. The binary is placed in ~/.keploy/bin. If your shell reports command not found, open a new terminal or add that folder to your PATH.
The sample repository ships with an old keploy/ folder of recorded tests. I moved it aside so the next steps start from a clean recording:
mv keploy keploy-shippedRecord test cases#
Recording means running your app under Keploy and using it normally. Keploy watches the traffic.
Start recording
Run this in a terminal and leave it open.
keploy record -c "./test-app-url-shortener"The -c flag is the command that starts your app. Keploy runs it for you, so you do not start the app yourself.
On the first run Keploy opens a browser tab to sign in to your Keploy account. The quickstart page does not mention this. Sign in and the command carries on. After that you should see the app boot next to Keploy's own logs. These are the lines that mattered to me:
INFO Keploy is intercepting this application natively on macOS
INFO Proxy started at port:16789
INFO Keploy agent is ready to record test cases and mocks.
PID: 40740
[GIN-debug] POST /url --> main.putURL (3 handlers)
INFO Started ingress forwarding {"orig_port": 8080, "new_port": 55405}Send some requests
Open a second terminal. First, shorten two URLs:
curl --request POST \
--url http://localhost:8080/url \
--header 'content-type: application/json' \
--data '{"url": "https://google.com"}'
curl --request POST \
--url http://localhost:8080/url \
--header 'content-type: application/json' \
--data '{"url": "https://keploy.io/docs"}'The responses I got:
{"ts":1790910561276549000,"url":"http://localhost:8080/Lhr4BWAi"}
{"ts":1790910561291329000,"url":"http://localhost:8080/PKSwnBrS"}The ts value will be different for you. The hash for https://google.com was Lhr4BWAi, the same one the quickstart shows, so the hash is derived from the URL.
Cover the error paths too
A good test set includes failures. Send a request with no URL, then follow a short link, then follow one that does not exist.
curl -s localhost:8080/url -H 'content-type: application/json' -d '{}'curl -i localhost:8080/Lhr4BWAicurl -s localhost:8080/doesnotexistI got a 400 with {"error":"missing url param"}, a 303 redirect to https://google.com, and a 404 with {"error":"url not found"}.
Stop recording
Go back to terminal 1 and press Ctrl+C.
Replay the tests#
First stop MongoDB so it cannot help:
docker compose stop mongoThen run the tests:
keploy test -c "./test-app-url-shortener" --delay 10keploy test starts your app again, waits --delay seconds for it to be ready, and then sends each recorded request. Ten seconds is the value the quickstart uses, and the run time of about 10 seconds below reflects it.
This is the summary I got, with MongoDB stopped:
TESTRUN SUMMARY. For test-set: "test-set-0"
Total tests: 5
Total test passed: 5
Total test failed: 0
Time Taken: "10.05 s"The app asked MongoDB questions. Keploy answered them from mocks.yaml.
Break a test on purpose#
A passing run does not prove much until you have seen one fail.
Change an expected value
I edited the expected body in keploy/test-set-0/tests/post-url-3.yaml from missing url param to missing url and ran the same test command again.
Keploy printed a side-by-side diff and marked the run as failed:
Testrun failed for testcase with id: "post-url-3"
│ EXPECT BODY │ ACTUAL BODY │
│ "error": "missing url" , │ "error": "missing url param" , │
Total tests: 5
Total test passed: 4
Total test failed: 1That is the regression signal you would get in CI if someone changed an error message by accident.
Put the value back
Open post-url-3.yaml again and change missing url back to missing url param. That is how I restored it. git checkout will not do it for you, because the sample repository does not track the test files you recorded.
Then run the tests once more. The summary should be back to 5 passed and 0 failed:
keploy test -c "./test-app-url-shortener" --delay 10Read what Keploy generated#
Everything Keploy saves is plain YAML inside the keploy/ folder.
keploy/
├── .gitignore
├── reports/test-run-0/test-set-0-report.yaml
└── test-set-0/
├── config.yaml
├── mappings.yaml
├── mocks.yaml
└── tests/
├── get-doesnotexist-1.yaml
├── get-lhr4bwai-1.yaml
├── post-url-1.yaml
├── post-url-2.yaml
└── post-url-3.yamlA test case#
This is the first request I recorded, trimmed to the interesting parts:
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":1790910561276549000,"url":"http://localhost:8080/Lhr4BWAi"}'
assertions:
noise:
body.ts: []
header.Date: []The noise block tells Keploy to ignore fields that change on every call. The response has a timestamp in ts and a Date header, and neither would ever match twice. The quickstart tells you to add body.ts here yourself. In my run Keploy had already filled it in, probably during the automatic replay after recording.
The mocks#
mocks.yaml held nine entries, all of kind Mongo. The first few are the driver's connection handshake, marked type: config. The rest are the real queries: an update when a URL is shortened and a find when one is looked up.
mappings.yaml links tests to mocks. For example, post-url-1 uses mock-3. post-url-3, the empty request, has no mock at all, because the handler returns 400 before it ever talks to MongoDB.
How Keploy works#
Here is the model I ended up with, based on the logs and on Keploy's docs.
- Keploy starts your app with a small interception layer attached. On Linux, Keploy's docs say this uses eBPF. On my Mac the log said
intercepting this application natively on macOS, and the command line showed aDYLD_INSERT_LIBRARIESlibrary being loaded into the app. - A local proxy sits between the outside world and your app. Requests to port 8080 pass through it, which is what the
Started ingress forwardingline refers to. - When your app opens a connection to MongoDB, Keploy sees the wire traffic and stores each request and response pair.
- On replay, Keploy plays the client role for HTTP. It sends the saved requests to your app. It plays the database role too, handing back the saved MongoDB responses.
Your code does not change, apart from the host name I edited earlier, which was needed for running outside Docker and has nothing to do with Keploy. There is no test helper to import and no mock to write.
Why this matters for Go developers#
Go makes unit testing pleasant, but the usual way to test a handler that calls a database is to define an interface, write a fake, and keep that fake in step with the real driver. This sample has none of that. The handler calls col.FindOne and col.UpdateOne on the MongoDB driver directly.
With Keploy:
- You get tests for existing code without refactoring it for testability.
- The tests come from real traffic, so they include the odd cases you actually hit, like the unknown hash that returns
404. - They run fast and offline, because nothing real is called.
- Test cases are readable YAML files that you can review in a pull request.
There are trade-offs. Recorded tests describe what the app did when you recorded, not what it should do. If the first behaviour was wrong, Keploy will happily lock the wrong behaviour in. And when you change how the app queries MongoDB, the old mocks no longer match, so you re-record.
Common problems and fixes#
Two of these happened to me. The rest are mistakes the docs warn about, which I did not reproduce.
Problems I hit#
Docker commands fail with a docker.sock error
- Why it happens
- Docker Desktop was not running, so there was no daemon listening.
- How to identify it
- The error reads
failed to connect to the docker API at unix:///.../docker.sock. - Fix
- Open Docker Desktop and wait until it says it is running.
- Verify
docker infoprints a server version instead of an error. I saw29.5.3.
Keploy shuts down early and nothing is recorded
- Why it happens
- Keploy exits when the shell that launched it exits. I had started it in the background of a one-shot shell.
- How to identify it
- The log contains
parent client process exited; self-terminating. It also warns that Keploy's agent is not answering and that calls are reaching real destinations unrecorded. - Fix
- Run
keploy recordin a terminal tab that stays open. Send requests from a second tab. - Verify
- Requests return JSON, and new files appear in
keploy/test-set-0/tests/.
Mistakes to avoid#
These come from the quickstart and installation docs. I did not hit them, so treat them as guidance and not as war stories.
- Leaving the MongoDB host as
mongoDb. That name only resolves inside the Docker Compose network. Running the app on the host needslocalhost:27017. - Using
go runon macOS. The quickstart advises running a built binary there. Keploy's macOS notes also say wrapper commands such asnpm startor a make recipe lose the interception, so give-cthe real executable. - Recording with an older Keploy and expecting the old file layout. The quickstart page still shows
test-1.ymland av1beta2header. My version wrotepost-url-1.yamlwithv1beta1. The idea is the same, but file names differ.
Verification checklist#
Tick these off against your own run:
-
keploy --versionprints a version. -
docker psshowsmongoDBbefore recording. -
keploy recordshowsKeploy agent is ready to record test cases and mocks. - Five test YAML files exist in
keploy/test-set-0/tests/. -
mocks.yamlexists and containskind: Mongoentries. - MongoDB is stopped, and
keploy teststill reports all tests passed. - Editing one expected value makes exactly one test fail.
Clean up#
When you are finished, tidy up from the gin-mongo folder.
Remove the MongoDB container
This stops MongoDB if it is still running and removes its container.
docker compose downRemove the Docker network
Compose leaves keploy-network alone because the Compose file marks it as external. Remove it yourself:
docker network rm keploy-networkReset the sample (optional)
To get the folder back to how you cloned it, delete your recording and the keploy.yml that Keploy created, put the shipped tests back and undo the main.go edit:
rm -rf keploy keploy.yml && mv keploy-shipped keploy && git checkout main.goWhat you learned#
- Keploy turns live requests into test cases and the app's database calls into mocks.
keploy record -c "<app command>"captures, andkeploy test -c "<app command>" --delay <seconds>replays.- Replay works without the database, which makes the tests quick and dependable.
- Fields that change on every call, like timestamps, belong in
noise. - Stopping a recording also runs a first replay, so you get feedback right away.
Next steps#
- Record more behaviour, for example the
/verify-emailroute, and re-run the tests. - Read the Keploy docs on recording filters and the configuration file.
- Re-run the suite after you change code. After my failing run, the CLI suggested
keploy test -c './test-app-url-shortener' --mappingsfor re-running after a fix. Its notes on CI use the same command and expect aKEPLOY_API_KEYsecret. - Try the other Go quickstarts and compare how each database is mocked.