{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/go/mocking-libraries-bleh/",
  "description": "Practical patterns for mocking in Go without external libraries. Learn to mock functions, methods, interfaces, HTTP calls, and time using only the standard library",
  "path": "/go/mocking-libraries-bleh/",
  "publishedAt": "2026-01-23T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "Testing",
    "API"
  ],
  "textContent": "> There are frameworks that generate those kind of fakes, and one of them is called\n> GoMock... they're fine, but I find that on balance, the handwritten fakes tend to be\n> easier to reason about and clearer to sort of see what's going on. But I'm not an\n> enterprise Go programmer. Maybe people do need that, so I don't know, but that's my\n> advice.\n>\n> -- Andrew Gerrand, [Testing Techniques (46:44)]\n\nNo shade against mocking libraries like [gomock] or [mockery]. I use them all the time, both\nat work and outside. But one thing I've noticed is that generating mocks often leads to\npoorly designed tests and increases onboarding time for a codebase.\n\nAlso, since almost no one writes tests by hand anymore and instead generates them with LLMs,\nthe situation gets more dire. These ghosts often pull in all kinds of third-party libraries\nto mock your code, simply because they were trained on a lot of hastily written examples on\nthe web.\n\nSo the idea of this post isn't to discourage using mocking libraries. Rather, it's to show\nthat even if your codebase already has a mocking library in the dependency chain, not all of\nyour tests need to depend on it. Below are a few cases where I tend not to use any mocking\nlibrary and instead leverage the constructs that Go gives us.\n\nThis does require some extra song and dance with the language, but in return, we gain more\ncontrol over our tests and reduce the chance of encountering [spooky action at a distance].\n\nMocking a function\n\nSay you have a function that creates a database handle:\n\nThe problem is that sql.Open hands the DSN directly to the driver. When you call\nOpenDB(\"admin\", \"secret\", \"db.internal\", \"orders\"), the function formats the DSN string\nand hands it to the MySQL driver. You can't intercept that call, you can't control what it\nreturns, and you probably don't want unit tests leaning on a real driver (or a real MySQL\ninstance) just to verify DSN formatting.\n\nThe fix is to make the database opener injectable:\n\nHere:\n\n- (1) defines a function type that matches sql.Open's signature\n- (2) accepts an opener function as a parameter\n- (3) delegates to that function instead of calling sql.Open directly\n\nIn production, pass the real sql.Open:\n\nHere:\n\n- (1) the real sql.Open is passed as the last argument - no wrapper needed\n\nIn tests, pass a fake that captures what was passed or returns canned values:\n\nHere:\n\n- (1) the fake captures the DSN for later assertion\n- (2) the call site looks the same, just with a different opener\n\nThis pattern works for any function dependency - UUID generators, random number sources,\nfile openers. Functions are first-class values in Go, so you can pass them around like any\nother value.\n\nThe downside is that parameter lists can grow quickly. If OpenDB also needed a logger, a\nmetrics client, and a config loader, the signature becomes unwieldy. When you find yourself\npassing more than two or three function dependencies, consider grouping them into a struct\nwith an interface - see [Mocking a method on a type].\n\nMonkey patching\n\nSometimes you inherit code where refactoring the function signature isn't practical. Maybe\nit's called from dozens of places, or it's part of a public API you can't change:\n\nThe Kafka writer is instantiated directly inside the function. There's no seam to inject a\nfake without touching every call site. If this function is called from 50 places in your\ncodebase, changing its signature means updating all 50.\n\nOne workaround is a package-level variable that points to the constructor:\n\nHere:\n\n- (1) define an interface with only the methods we need from kafka.Writer\n- (2) the package variable returns the interface type, not the concrete type\n- (3) the function calls it instead of instantiating directly\n\nProduction code doesn't change - it calls PublishOrderCreated exactly as before, and the\ndefault newWriter creates real Kafka writers.\n\nTests swap it out:\n\nHere:\n\n- (1) the fake captures the message key for later assertion\n- (2) t.Cleanup ensures the original is restored even if the test fails\n- (3) the replacement factory returns the fake - note it returns kafkaWriter, matching the\n  variable's type\n- (4) assert the captured key matches the expected value\n\nThis works, but be aware of the costs. Tests that mutate package state can't run in\nparallel - they'd stomp on each other's fakes. If you're writing tests from an external\npackage (package events_test), the variable must be exported, which pollutes your public\nAPI.\n\nPrefer the [function parameter pattern] or the [interface pattern] over monkey patching.\nReserve this technique for legacy code where changing signatures would be too disruptive.\n\nMocking a method on a type\n\nThis is a pattern you'll see all the time in services that integrate with third-party APIs.\nHere's a payment service that charges customers through Stripe (this uses the newer\nstripe.Client API, which is the recommended shape in recent stripe-go versions):\n\nTesting this hits the real Stripe API. That's slow, requires live credentials, and in\nproduction mode charges actual money. The problem is that s.client is a stripe.Client\nfrom the SDK - there's no way to swap it for a fake without introducing a seam.\n\nThe solution is to introduce an interface that describes what you need:\n\nHere:\n\n- (1) the interface has one method matching what we need from the SDK\n- (2) the service holds the dependency as a field\n- (3) calls through the interface instead of the client directly\n\nIn production, inject the real Stripe service client:\n\nHere:\n\n- (1) sc.V1PaymentIntents satisfies PaymentIntentCreator (it has a Create method with\n  the right signature)\n\nIn tests, you pass a fake that returns canned values:\n\nHere:\n\n- (1) the fake struct holds the canned return value\n- (2) returns whatever you configured instead of calling Stripe\n- (3) configure the fake with the expected payment intent ID\n\nThe service doesn't know or care whether it's talking to Stripe or a test fake. This is the\nmost common mocking pattern in Go - define an interface for your dependency, accept it in\nyour constructor, and swap implementations at runtime.\n\nBut what happens when the SDK surface area is huge and your code only needs one operation?\nThat's where the next pattern comes in.\n\nConsumer-side interface segregation\n\nThe previous pattern works well when you control the interface. But AWS SDK clients have\ndozens of methods. The DynamoDB client has over 40 operations - GetItem, PutItem,\nQuery, Scan, BatchGetItem, and so on. If you write tests against a dependency that\nexposes the whole surface area, your fakes become annoying fast.\n\nThe solution is to define a minimal interface on the consumer side:\n\nHere:\n\n- (1) the interface has exactly one method - just what this function needs\n- (2) accept the minimal interface and call through it\n\nIn production, pass the real DynamoDB client - it satisfies itemGetter because it has a\nGetItem method. Go interfaces are satisfied implicitly:\n\nHere:\n\n- (1) the real client satisfies itemGetter automatically - no adapter or wrapper needed\n  thanks to implicit interface satisfaction\n\nIn tests, you only implement the one method you need:\n\nHere:\n\n- (1) the fake struct holds the canned response data\n- (2) returns the configured item - no network call\n- (3) pass the fake to the function under test\n\nThis is the [Interface Segregation Principle] in action - clients shouldn't be forced to\ndepend on methods they don't use.\n\nBut this approach has limits. If you have 20 functions each using different DynamoDB\noperations, you'd end up with 20 tiny interfaces. And sometimes you're stuck with a\npreexisting interface type that has more methods than you want. That's where struct\nembedding helps.\n\nStruct embedding for partial implementation\n\nSometimes you can't define your own minimal interface. Maybe a library insists on a specific\ninterface type, and it's bigger than what your test cares about.\n\nThe AWS SDK v2's S3 upload manager is a good example. manager.NewUploader takes a client\ninterface that supports both single-part uploads and multipart uploads. If your test is\nexercising the single-part path and you only want to intercept PutObject, implementing the\nmultipart methods just to satisfy the interface is pure busywork.\n\nGo's struct embedding provides an escape hatch. Here's the production code:\n\nHere:\n\n- (1) accepts the SDK's UploadAPIClient interface - a large interface with many methods\n\nIn tests, embed the interface in your fake and override only what you need:\n\nHere:\n\n- (1) embedding the interface satisfies the full interface at compile time\n- (2) capture what you care about - only implement what this test needs\n- (3) pass the fake to code that expects the full UploadAPIClient interface\n- (4) use a small body so the upload manager takes the single PutObject path\n\nThe embedded interface value is nil, so any method you don't override will panic if\ncalled. This is a feature, not a bug. If your code accidentally triggers multipart and calls\nCreateMultipartUpload, the test crashes immediately, and you learn that your test setup\n(or your assumptions) are wrong.\n\nFunction type as interface\n\nFor interfaces with a single method, there's an even more compact approach. Say you have\nmiddleware that validates authentication tokens:\n\nHere:\n\n- (1) a single-method interface - the perfect candidate for a function type adapter\n- (2) the middleware accepts the interface as a dependency\n\nYou could write a fake struct with a Validate method, but Go lets you define a function\ntype that satisfies the interface:\n\nHere:\n\n- (1) define a function type with the right signature\n- (2) add a method that just calls the function itself\n\nThis is the same pattern the standard library uses with http.HandlerFunc. Now tests can\npass inline functions:\n\nHere:\n\n- (1) return a known user ID for a valid token\n- (2) the middleware accepts it as a TokenValidator interface\n\nNo extra struct definitions cluttering up your test file.\n\nMocking HTTP calls\n\nWhen your code makes HTTP requests to external services, the net/http/httptest package\nprovides a test server that runs on localhost. Say you have a client that fetches exchange\nrates:\n\nIn production, c.baseURL points to the real API. Testing against it is problematic - it's\nslow, requires credentials, returns different values each time, and might rate-limit your\nCI.\n\nThe httptest.Server spins up a real HTTP server on localhost:\n\nHere:\n\n- (1) spin up a local HTTP server with a handler that returns canned JSON\n- (2) shut down the server when the test finishes\n- (3) point your client at srv.URL instead of the real API\n\nYour code makes real HTTP calls over TCP, but they never leave the machine. You can return\ndifferent responses for different scenarios - rate limits, malformed JSON, network errors -\nwhatever you need to test.\n\nMocking time\n\nThis is essentially the same technique as [Mocking a function] - we're just applying it to\ntime.Now. Code that depends on the current time is tricky to test:\n\nEvery call to time.Now() returns a different value. You can't write a reliable test\nbecause the result depends on when the test runs.\n\nMake the clock injectable:\n\nHere:\n\n- (1) define a function type for getting the current time\n- (2) accept it as a parameter\n\nIn production, pass time.Now:\n\nHere:\n\n- (1) pass the real time.Now function - it satisfies the Clock type\n\nIn tests, pass a function that returns a fixed time:\n\nHere:\n\n- (1) a clock that returns one hour before expiry\n- (2) a clock that returns one hour after expiry\n\nFor code that uses time.Sleep, timers, or tickers, Go 1.25's [testing/synctest] provides a\nfake clock that advances automatically when goroutines in the bubble are [durably blocked]:\n\nHere:\n\n- (1) synctest.Test runs the function in an isolated bubble with fake time starting at\n  2000-01-01\n- (2) the ticker uses fake time - no real 10-second waits\n- (3) time.Sleep inside the bubble uses fake time; time advances when goroutines are\n  durably blocked, so this returns instantly after the ticker fires 3 times\n- (4) synctest.Wait is a synchronization point; it blocks until the other goroutines in\n  the bubble are durably blocked or finished\n\nInside synctest.Test, the framework intercepts time operations. The test completes\ninstantly rather than waiting for real time to pass.\n\nClosing words\n\nThese are the most common ones where I typically avoid opting for mocking libraries. But\nthere are cases when I still like to generate mocks for an interface. One example that comes\nto mind is testing gRPC servers. I'm sure I'm forgetting some other cases where I regularly\nuse mocking libraries.\n\nThe point is not to discourage the use of mocking libraries or to make a general statement\nthat \"all mocking libraries are bad.\" It's that these mocking libraries have costs\nassociated with them. Code generation is fun, but it's one extra step that you have to teach\nsomeone who's onboarding to your codebase.\n\nAlso, if you're using LLMs to generate tests, you may want to write some tests manually to\ngive the tool a sense of how you want your tests written, so it doesn't pull in the universe\njust to mock something that can be mocked natively using Go constructs.\n\nFor more on why handwritten fakes often beat generated mocks, see [Test state, not\ninteractions].\n\n\n\n\n\n[gomock]:\n    https://github.com/uber-go/mock\n\n[mockery]:\n    https://github.com/vektra/mockery\n\n[spooky action at a distance]:\n    https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_programming)\n\n[mocking a method on a type]:\n    #mocking-a-method-on-a-type\n\n[mocking a function]:\n    #mocking-a-function\n\n[function parameter pattern]:\n    #mocking-a-function\n\n[interface pattern]:\n    #mocking-a-method-on-a-type\n\n[test state, not interactions]:\n    /go/test-state-not-interactions/\n\n[interface segregation principle]:\n    /go/interface-segregation/\n\n[testing/synctest]:\n    https://pkg.go.dev/testing/synctest\n\n[durably blocked]:\n    https://pkg.go.dev/testing/synctest#hdr-Blocking\n\n[Testing Techniques (46:44)]:\n    https://www.youtube.com/watch?v=ndmB0bj7eyw&t=2804s",
  "title": "Your Go tests probably don't need a mocking library"
}