opensourceprojects.dev

A broadsheet for software that doesn't ask for your email

Monkey patching in Go unit tests with gomonkey, just disable inlining first
GitHub RepoImpressions434

Project Description

View on GitHub

Monkey Patching in Go? Yes, gomonkey Makes It Possible (But Watch the Gotcha)

You know that feeling when you need to test a tricky function that calls a package-level variable or an external API, and there's no clean way to inject a mock? In Go, you usually refactor to interfaces, or you write an integration test with a real dependency. But sometimes, you just need a quick patch — something like Python's unittest.mock.patch or Ruby's monkey_patch.

That's where gomonkey comes in. It lets you monkey-patch functions and methods at runtime for unit tests. But there’s a catch: you have to disable inlining, or your patches silently won't apply.

What It Does

gomonkey is a Go library for monkey patching. It can:

  • Replace the implementation of any function or method at runtime
  • Patch functions from third-party packages
  • Apply, verify, and revert patches during tests

Think of it as a way to hijack a function pointer, but in a compiled language like Go, that’s usually impossible without unsafe tricks. Gomonkey does this by modifying the machine code in memory — it overwrites the function's entry point with a jump to your replacement. It’s clever, and it works reliably on amd64 and arm64 architectures.

Why It’s Cool

The most obvious use case is unit testing code that depends on global state, time, or external services without refactoring everything to interfaces. But gomonkey also shines in a few specific ways:

  • No interface contract needed – You can patch any exported or unexported function directly. This is a lifesaver when you're dealing with legacy code that uses package-level functions like os.Getenv or time.Now.
  • Built-in patch verification – It can count how many times a patched function was called, and with what arguments, which is great for asserting behavior.
  • Supports method and function chaining – You can patch a method on a struct, even if the struct has dependencies. This is rare in Go test libraries.

The real hack, though, is how it works. At the assembly level, gomonkey writes a jump instruction at the start of the target function's memory. That’s why you must disable inlining — inlined functions don't have their own entry point to overwrite. You add //go:noinline to functions you want to patch, or compile your tests with -gcflags=-l to disable inlining globally.

How to Try It

First, install the package:

go get github.com/agiledragon/gomonkey/v3

Then write a test. Here’s a minimal example that patches time.Now to return a fixed timestamp:

package main

import (
    "testing"
    "time"
    "github.com/agiledragon/gomonkey/v3"
)

func TestPatchTimeNow(t *testing.T) {
    fixedTime := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
    patches := gomonkey.ApplyFunc(time.Now, func() time.Time {
        return fixedTime
    })
    defer patches.Reset()

    now := time.Now()
    if !now.Equal(fixedTime) {
        t.Errorf("expected %v, got %v", fixedTime, now)
    }
}

When you run this test, use the -gcflags=-l flag to disable inlining:

go test -gcflags=-l ./...

If you forget the -gcflags flag, your patch will silently fail — the test might still pass if your code doesn’t actually call the original function, but the behavior is undefined. Always double-check with a quick t.Log or by inspecting the patch count.

Final Thoughts

Gomonkey is one of those tools you reach for when you're stuck in a corner with tight deadlines. It's not the "Go way" — idiomatic Go prefers interfaces and dependency injection — but real-world code doesn't always follow the rules. If you use it sparingly and only in tests (never in production), it can save hours of refactoring.

The main downside is the inlining gotcha. It's easy to forget, and debugging why a patch didn't apply is frustrating. Also, because it modifies machine code at runtime, it can cause panics if the patch target is already executing on another goroutine. Use it for deterministic, single-threaded test scenarios.

Bottom line: if you need a quick, dirty patch for a test and you don't mind adding -gcflags=-l to your test command, gomonkey gets the job done. Just don't tell the Go core team you're using it.


Found this useful? Follow us for more developer tools and tips.
@githubprojects

Back to Projects
Project ID: 94b43cc0-77d3-404e-a97f-1ab646382fdbLast updated: July 29, 2026 at 02:45 AM