Mockey, runtime function mocking for Go without interfaces
A simple and easy-to-use Go mocking library derived from ByteDance's internal best practices
At a glance
- What is it?
- Mockey is ByteDance's open source Go mocking library. It patches functions, methods and variables at runtime by rewriting instructions, which removes the need to define an interface for every dependency, and it requires the compiler to be told not to inline or optimise. That requirement is the whole trade-off.
- Who is it for?
- Mockey suits a Go codebase with a large, sprawling dependency graph where adding interfaces to production code is not an option, and where the team accepts turning off inlining in tests. It does not suit a project that wants its production code to be mockable by design, because a dependency you never abstracted is a dependency you can only stub at runtime.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Patching at runtime instead of abstracting at compile time
Mockey is described as a simple and easy-to-use Go mock library, derived from ByteDance's internal best practices, and it is described as widely used in the unit test writing of ByteDance services across 7k+ repositories, with the project actively maintained.
The mechanism is the part that distinguishes it. In essence, it rewrites function instructions at runtime, similarly to the monkey patching library or gomonkey. It does not call an interface, and it does not need the production code to declare one.
That is the problem it solves. The standard Go approach to testability is to define an interface at the consumer and inject a fake, which means every dependency you want to stub has to be abstracted, and every implementation has to satisfy it. Mockey removes that requirement. You can mock a plain function, a method on an unexported type, a function variable or a package-level variable, and the code under test needs no change at all.
The cost of removing the abstraction is that the seam exists only at runtime and only in the test binary. There is no compile-time check that the mock matches the real signature, and the mock applies to whatever the linker actually emitted, which is why the compiler settings matter so much.
The project is Apache-2.0 licensed, with a release badge and a codecov badge at the top of the README. The most recent release is v1.4.8, tagged on 2026-10-16, and the last push to the repository was on the same day.
Inlining and optimisation have to be off, or nothing works
The README puts this first, in a note rather than a footnote: Mockey requires inlining and compilation optimization to be disabled during compilation, or it will not work. The FAQ section of the document is the place where the exact flags are described.
The reason is mechanical rather than arbitrary. Mockey rewrites the instructions of the function it is replacing. The Go compiler's inliner replaces a small function's body with a call at the call site, so there is no single instruction sequence left to rewrite, and the patch either fails or silently does nothing. The same applies to optimisations that fold or restructure the code.
The second note in the README is a recommendation rather than a requirement: it is strongly recommended to use Mockey together with the goconvey library in unit tests. That recommendation is about lifecycle rather than syntax, because Mockey provides PatchConvey and PatchRun, which automatically release mocks after each test case, and a mock that survives into the next test case is the classic failure mode of runtime patching.
So the practical requirement for a team adopting Mockey is that its test build differs from its production build. The production binary keeps the optimisations; the test binary turns them off. That is a real cost in build configuration, and it is the main reason a project might choose interfaces instead.
Mock, Return, Build
The API is a builder chain with three verbs. Mock selects the target, Return sets what it returns, and Build makes the mock effective.
The simplest example mocks a standard library function:
package main
import (
"fmt"
"math/rand"
. "github.com/bytedance/mockey"
)
func main() {
Mock(rand.Int).Return(1).Build() // mock `rand.Int` to return 1
fmt.Printf("rand.Int() always return: %v\n", rand.Int()) // Try if it's still random?
}The unit test form uses goconvey, and the mock is scoped by PatchConvey so it is released when the case ends:
func TestWin(t *testing.T) {
PatchConvey("TestWin", t, func() {
Mock(rand.Int).Return(100).Build() // mock
res1 := Win(101) // execute
So(res1, ShouldBeTrue) // assert
res2 := Win(99) // execute
So(res2, ShouldBeFalse) // assert
})
}The library handles plain functions, methods with value receivers and methods with pointer receivers, and the syntax differs in the last case, because a pointer receiver method is expressed as a pointer expression. The README notes one detail that trips people up: if the target has no return value, you still need to call the empty Return(), or use To to customise the hook function.
Beyond the basics the feature list is long: supporting hook functions, GetMethod for special cases such as unexported types, unexported methods and methods in nested structs, interface mocking as an experimental feature, conditional mocking, sequence returning, the decorator pattern for executing the original function after mocking, goroutine filtering with inclusion, exclusion and targeting, and acquiring a Mocker for advanced use such as getting the execution times of the target or the mock function. Variables are mocked too, both common variables and function variables.
Generics needed two releases to stop interfering with each other
Starting from v1.3.0, Mock experimentally adds the ability to automatically identify generics for Go 1.20 and later, so you can use Mock to replace a generic function directly, and MockGeneric handles the generic cases explicitly:
MockGeneric(FooGeneric[string]).Return("MOCKED!").Build()The interesting part is the bug that came with it, because it is the kind of thing the documentation is unusually honest about. Go generics share an implementation for different types with the same underlying type, so in a type MyString string, MyString and string share one implementation. Mocking one therefore interfered with the other, and the README shows a case where mocking the string instantiation also changed the behaviour of the MyString instantiation.
That was resolved in v1.3.1, which supports mocking generic functions and methods with the same gcshape but different actual types, and the README shows the corrected behaviour with two separate mockers, one per type, each affecting only its own instantiation.
The remaining sharp edge is visible in the same examples: a generic mock is type-specific, so MockGeneric(FooGeneric[string]) does not affect FooGeneric(1), and the README comments that the integer call does not work because of a type mismatch. A generic function mocked for one instantiation is not mocked for another, which is arguably correct but is the kind of detail that produces a confusing test failure when the type argument is inferred rather than explicit.
The compatibility matrix has a moving floor
The README's compatibility section is short and specific.
On operating systems, Mockey supports macOS on Darwin, Linux and Windows. On architectures, AMD64, ARM64 and RISC-V64, with the RISC-V64 support noted as Linux only. On Go versions, the current requirement is Go 1.24 or newer, and for Go 1.23 and earlier the guidance is to use Mockey v1.4.6. There is one more line, and it is the kind that bites in CI: Linux on RISC-V64 requires Go 1.26 or newer.
The Go module matches, with go 1.24.0 declared in go.mod and three direct dependencies: goconvey at v1.7.2, golang.org/x/arch at v0.23.0 and golang.org/x/sys at v0.26.0. The architecture dependency is the giveaway for how the patching works, since x/arch provides the instruction-level view of the code that has to be rewritten.
The versioning story is therefore a moving target rather than a stable one, and the v1.4.6 pointer for older Go versions is an explicit instruction to pin an older release for older toolchains. A team with a mix of Go versions across build machines is looking at two Mockey versions in one repository.
The repository layout follows the feature list closely. There are mock.go for the core, mock_option.go, mock_condition.go for conditional mocking, mock_sequence.go for sequence returning, mock_generics.go for the generic support, mock_var.go for variable mocking, private.go for the internals, global.go, utils.go and utils_option.go, and an exp/ directory for experimental work. The test files mirror that structure, with separate ones for generics above and below Go 1.20.
Where runtime patching is the wrong approach
There are three situations where Mockey is the wrong choice, and they are not unusual.
The first is a codebase that already has good dependency injection. If your services take interfaces and your constructors take them, a hand-written fake or a generated mock is faster, safer and checked by the compiler. Mockey exists for the case where that structure is absent, and using it where interfaces are available trades a compile-time guarantee for a runtime one.
The second is a build that cannot turn off optimisation. If your test binary has to keep inlining, Mockey does not work, and the README says so without hedging. Workarounds exist in the wider ecosystem but they are not what Mockey documents, and adopting a mocking library whose central requirement your build cannot meet is a bad trade.
The third is code that runs in production-like environments where patching code at runtime is unacceptable. Monkey patching rewrites instructions in a running process, and in some environments, hardened runtimes, certain platforms, or anything with strict memory protection, that is not possible or is forbidden.
There is also a testing-philosophy point that the library does not make but that a team should. A mock that patches any function in the process can mask a real integration problem, because the test passes with a patched dependency that behaves differently from the real one. That is manageable with discipline, and it is one reason the README pairs Mockey with PatchConvey so that mocks are released after each case.
Editorial conclusion
Mockey suits a Go codebase with a large, sprawling dependency graph where adding interfaces to production code is not an option, and where the team accepts turning off inlining in tests. It does not suit a project that wants its production code to be mockable by design, because a dependency you never abstracted is a dependency you can only stub at runtime. Before you adopt it, check that your build flags disable inlining and optimisation for the test binary, since the README is explicit that it will not work otherwise, and confirm your Go version and platform are on the supported list, which for RISC-V64 on Linux means Go 1.26 or newer.
Frequently asked questions
How do I install Mockey and mock a function?
Install it with go get github.com/bytedance/mockey@latest, then call Mock on the target, Return with the value it should produce and Build to make the mock effective, for example Mock(rand.Int).Return(1).Build(). If the target has no return value you still need to call the empty Return() or use To for a custom hook.
Why does Mockey require the compiler to disable inlining?
Mockey rewrites the instructions of the function it replaces at runtime, and the Go inliner folds a small function body into its call site, which leaves no single instruction sequence to rewrite. The README states that inlining and compilation optimisation must be disabled during compilation or Mockey will not work, and points to its FAQ section for the exact flags.
Which Go versions and platforms does Mockey support?
The current requirement is Go 1.24 or newer, with Go 1.23 and earlier to use Mockey v1.4.6, and Linux on RISC-V64 requiring Go 1.26 or newer. The supported systems are macOS on Darwin, Linux and Windows, on AMD64, ARM64 and RISC-V64 with RISC-V64 limited to Linux.
What advanced mocking features does Mockey offer?
Beyond plain functions, methods and variables it lists hook functions, PatchConvey and PatchRun for automatic release after each test case, GetMethod for unexported and nested cases, interface mocking as an experimental feature, conditional mocking, sequence returning, the decorator pattern that runs the original function after mocking, goroutine filtering with inclusion, exclusion and targeting, and a Mocker handle for advanced use such as execution counts.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/bytedance-mockey)Was this useful?