Keploy logo
Go logo

Keploy as a Go testing framework

Keploy tests Go services by recording real HTTP and database traffic from a running binary, then replaying it as assertions. There are no interfaces to extract for mocking, no httptest.Server to stand up, and no sqlmock expectations to keep in sync with your queries.

Generate Go tests free
keploy record -c "./orders-service"
18.4K+VS Code1.2M+300M+mocks created

What Keploy gives a Go team

Any Go binary, unchanged

Keploy runs the binary you build and ship. net/http, Gin, Echo, Fiber, chi, and gRPC servers all record the same way, because interception happens at the socket rather than inside the router.

  • Go 1.19 and above
  • No build tag or test-only entrypoint
  • Router and middleware choice is invisible
  • HTTP and gRPC servers both supported
The problem

Why Go integration tests slow teams down

The friction is rarely the assertions. It is everything around them — spinning up dependencies, keeping mocks honest, and repairing tests after every refactor.

Three ways to do it

Testing Go: by hand, with testify + sqlmock, or with Keploy

testify and sqlmock need an interface to substitute and a query string to restate. Keploy captures outside the process, so concrete types stay concrete and no contract is written down twice.

Select any row for the full comparison, with code.

Same coverage, three costs

What you write for Go vs what Keploy records

All three produce the same assertion. Only the third still passes after the next refactor without anyone editing it.

By hand2–4 hours
orders_manual_test.go
hand-written
// Hand-written: fakes, request building, and assertions by hand.
package orders
 
import (
"net/http/httptest"
"strings"
"testing"
)
 
type fakeRepo struct{}
 
func (fakeRepo) Get(id string) (*Order, error) {
return &Order{ID: id, AmountMinor: 4200, State: "NEW"}, nil
}
 
type fakePayments struct{}
 
func (fakePayments) Charge(tok string) (string, error) {
return "AUTHORIZED", nil
}
 
func TestConfirmOrder(t *testing.T) {
h := NewHandler(fakeRepo{}, fakePayments{})
body := strings.NewReader(`{"paymentToken":"tok_123"}`)
req := httptest.NewRequest("POST", "/orders/ord_1/confirm", body)
rec := httptest.NewRecorder()
 
h.ServeHTTP(rec, req)
 
if rec.Code != 200 {
t.Fatalf("got %d, want 200", rec.Code)
}
}

Two fake types and two interfaces exist only for this test. Both encode a payload shape nothing checks against the real Postgres rows or Stripe response.

testify + sqlmock1–2 hours
orders_testify_test.go
tool-assisted
// testify + go-sqlmock + httptest.
package orders
 
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
)
 
func TestConfirmOrder(t *testing.T) {
db, mock, _ := sqlmock.New()
defer db.Close()
 
// The query is now written twice: here, and in the repository.
rows := sqlmock.NewRows([]string{"id", "amount_minor", "state"}).
AddRow("ord_1", 4200, "NEW")
mock.ExpectQuery("SELECT id, amount_minor, state FROM orders").
WithArgs("ord_1").
WillReturnRows(rows)
 
upstream := httptest.NewServer(stripeStub())
defer upstream.Close()
 
h := NewHandler(NewRepo(db), NewPayments(upstream.URL))
rec := post(h, "/orders/ord_1/confirm", `{"paymentToken":"tok_123"}`)
 
assert.Equal(t, 200, rec.Code)
assert.NoError(t, mock.ExpectationsWereMet())
}

Idiomatic Go, and still two copies of every contract — the SQL string is duplicated and stripeStub() is a handler someone has to keep current by hand.

With Keploy~5 minutes
test-1.yaml
auto-generated
# Recorded with: keploy record -c './server'
# Nobody wrote this file. Keploy captured it from a real request.
version: api.keploy.io/v1beta1
kind: Http
name: test-1
spec:
req:
method: POST
url: /orders/ord_1/confirm
body: '{"paymentToken":"tok_123"}'
resp:
status_code: 200
body:
id: "ord_1"
state: "CONFIRMED"
amountMinor: 4200
confirmedAt: "2026-09-02T11:04:18Z"
noise:
- body.confirmedAt
# No interface, no sqlmock expectation, no httptest server.
mocks:
- kind: Postgres
operation: "SELECT id, amount_minor, state FROM orders WHERE id = $1"
- kind: Http
url: https://api.payments.example.com/v1/payment_intents/tok_123/confirm
status: 200

The SQL and the upstream response are recorded, not restated. Concrete types stay concrete because nothing in the binary had to become an interface.

Times are estimates for authoring one endpoint’s coverage from scratch, not measurements.

Keploy vs the alternatives

Go testing tools, compared

The options a team on Go actually reaches for, and where each one genuinely wins. Select a row for the full comparison.

Best in classStrongPartialNot covered

Assessments reflect each tool’s documented behaviour, not benchmark measurements.

How it works

Record your Go app once, replay it forever

Keploy sits below your Go process at the network layer. It watches the calls your app already makes, then serves them back on replay so tests run with no dependencies attached.

Keploy records a GET call to /api/v1/orders/{id} on a Go service and captures the dependency calls it makes.

An example shape of a captured call. Your own endpoints and dependencies come from your real traffic, so nothing here has to be written by hand.
Mock coverage

What Keploy mocks for Go, with zero config

7 of the 8 dependencies a typical Go service talks to are stubbed from the recording itself — no mock classes, no fixture files, no containers in CI.

Quick start

Your first Go test suite in under five minutes

Every command below runs against your existing Go service. Nothing in your source tree changes.

  1. 1Install the Keploy CLI

    A single binary. Nothing is added to go.mod, so your module graph is untouched.

    curl -sSL https://keploy.io/install.sh | bash
  2. 2Build and record

    Build as normal, then hand the binary to Keploy instead of running it directly.

    go build -o server ./cmd/server
    keploy record -c "./server"
  3. 3Exercise your endpoints

    Every request served while recording becomes a test case, with its database and HTTP calls captured alongside.

    curl -X POST localhost:8080/orders/ord_1/confirm -H 'Content-Type: application/json' -d '{"paymentToken":"tok_123"}'
  4. 4Replay in CI

    Replay boots the binary against recorded mocks, so the job needs no service containers.

    keploy test -c "./server" --delay 5

Ready to try it on your own Go service?

Ecosystem

Works with the rest of your Go stack

Keploy records at the network layer, so framework and driver choices inside your Go app do not change how it captures traffic.

FAQ

Go testing with Keploy: common questions

Join our GlobalCommunity

Connect with developers worldwide. Follow updates, ask questions, share feedback, and ship faster with other Keploy builders.

1.2M+Installs
18.4K+GitHub
100K+Devs
300M+Mocks
1K+Contributors
#1OSS Trending
4.9★★★★★from 500+ reviews onG2GartnerVS CodeChrome
★★★★★

Best report of integration and API tests I've seen — which we don't get from RestAssured.

G2
★★★★★

Future of microservices testing. I don't write tests now!

G2 · 5/5
★★★★★

An amazing product that simplifies the automation.

Gartner · 4.0
XGitHubSlackYouTubeLinkedIn
Built by developers, for developers.Let's build the future, together.