A tool for generating self-contained, type-safe test doubles in go
A tool for generating self-contained, type-safe test doubles in go
counterfeiter Go code declares what it needs from its dependencies as interfaces, usually small ones defined by the package that uses them. Testing that code means supplying fake implementations of those interfaces. Go has no way to build one at runtime, so they are written by hand or generated.
counterfeiter generates test doubles for a given interface. Given this:
package foo
//go:generate go tool counterfeiter -generate
//counterfeiter:generate . MySpecialInterface
type MySpecialInterface interface {
DoThings(string, uint64) (int, error)
}go generate writes foofakes/fake_my_special_interface.go, and your tests can do this:
fake := &foofakes.FakeMySpecialInterface{}
fake.DoThingsReturns(3, nil)
num, err := fake.DoThings("stuff", 5)
Expect(num).To(Equal(3))
Expect(fake.DoThingsCallCount()).To(Equal(1))
str, _ := fake.DoThingsArgsForCall(0)
Expect(str).To(Equal("stuff"))counterfeiter is run by go generate, so fakes are regenerated alongside the code they fake. The steps below assume go 1.24 or later, which added go tool; for older versions of go, refer to an older version of this README.
counterfeiter as a tool dependencyEstablish a tool dependency on counterfeiter by running the following command:
go get -tool github.com/maxbrunsfeld/counterfeiter/v6Add one go:generate directive per package that runs counterfeiter -generate, and one counterfeiter:generate directive per interface you want a fake for. You can add them right next to your interface definitions (or not), in any .go file in the package.
In myinterface.go:
package foo
// You only need **one** of these per package!
//go:generate go tool counterfeiter -generate
// You will add lots of directives like these in the same package...
//counterfeiter:generate . MySpecialInterface
type MySpecialInterface interface {
DoThings(string, uint64) (int, error)
}
// Like this...
//counterfeiter:generate . MyOtherInterface
type MyOtherInterface interface {
DoOtherThings(string, uint64) (int, error)
}A counterfeiter:generate directive takes the same arguments as the command line: the directory of the package that declares the interface (. for the package the directive is in) and the name of the interface, plus any of the flags described below.
If a package only has an interface or two, you can skip -generate and the counterfeiter:generate directives, and run counterfeiter directly from one go:generate line per interface instead:
//go:generate go tool counterfeiter . MySpecialInterface-generate is much faster once there are several directives in a package, because it loads the package once and writes every fake from one process, where each go:generate line starts a new one.
go generateYou can run go generate in the directory with your directive, or in the root of your module (to ensure you generate for all packages in your module):
$ go generate ./...
Writing `FakeMySpecialInterface` to `foofakes/fake_my_special_interface.go`... Done
Writing `FakeMyOtherInterface` to `foofakes/fake_my_other_interface.go`... DoneMySpecialInterface above is declared in package foo, so //counterfeiter:generate . MySpecialInterface produces:
| default | change it with | |
|---|---|---|
| fake type | FakeMySpecialInterface (Fake + the interface name) |
-fake-name, -fake-name-template |
| file | fake_my_special_interface.go (the fake's name in snake case) |
-o .go |
| directory | foofakes/ (fakes, beside the package that declares the interface) |
-o |
| package | foofakes (fakes), or whatever package already lives in the output directory |
-o, -test, or a file declaring the package |
Every fake ends with a compile-time assertion that it satisfies the interface, so when the interface changes, the stale fake stops compiling until you run go generate again.
The examples below are counterfeiter:generate directives, and assume the package has the //go:generate go tool counterfeiter -generate line from Step 2. A package needs only one of those, no matter how many directives it has.
Flags given alongside -generate on the //go:generate line are the defaults for every //counterfeiter:generate directive in the package: -o, -header, -q, -test and -fake-name-template. A directive's own flags take precedence. So if you would rather keep all of a package's fakes in a fake package, named after their interfaces, you can write that once:
//go:generate go tool counterfeiter -generate -o fake -fake-name-template '{{.TargetName}}'
//counterfeiter:generate . MyRepository
//counterfeiter:generate . MyPresenter$ go generate ./...
Writing `MyRepository` to `fake/my_repository.go`... Done
Writing `MyPresenter` to `fake/my_presenter.go`... DoneYou can fake any interface your module can import. A third-party module has to be a dependency first (go get it, so it appears in your go.mod), since counterfeiter loads the interface from the module cache the same way the compiler would. Name the interface as ., or give the directory of the package and the interface name:
//counterfeiter:generate io.WriteCloser
//counterfeiter:generate github.com/redis/go-redis/v9.Pipeliner
//counterfeiter:generate ../otherpackage OtherInterfaceThe two forms differ in where the fake goes. With ., it goes into the fakes package of the directory the directive is in, so in package foo the first line writes foofakes/fake_write_closer.go. With a directory and an interface name, it goes into the fakes package beside that directory, so the third line writes ../otherpackage/otherpackagefakes/fake_other_interface.go. Use -o to put it somewhere else.
By default the fake lives in a sibling fakes package. Tests in itself cannot import it, because `fakes` imports and that would be an import cycle. To use a fake in those tests, point -o at the interface's own directory:
//counterfeiter:generate -o . . MySpecialInterfaceWhen the output directory is the directory of the package that declares the interface, counterfeiter generates the fake as a member of that package: it does not import the package, refers to its types unqualified, and can fake unexported interfaces too, in which case the fake is unexported as well (gadget gets fakeGadget) unless -fake-name names it. -o may also name a file in that directory, for example -o fake_my_special_interface_test.go to keep the fake out of the non-test build.
_testGo lets a directory hold a second package for tests, _test, which imports `` and sees only its exported API. If your tests are in it, -test generates the fake into that external test package instead, as a _test.go file in the current directory, next to the tests that use it:
//counterfeiter:generate -test . MySpecialInterface
//counterfeiter:generate -test ../otherpackage OtherInterface
//counterfeiter:generate -test io.WriteCloserIn package foo, all three of these write a _test.go file into the current directory, in package foo_test: fake_my_special_interface_test.go, fake_other_interface_test.go and fake_write_closer_test.go. With -test the fake goes into the package the directive is in, not the package that declares the interface, so faking OtherInterface writes nothing into ../otherpackage. The fakes are only compiled for tests, and the tests use them unqualified (&FakeMySpecialInterface{}). The interface's package is imported as usual, so the interface must be exported. With -o the fake goes into the external test package of that directory instead.
If the output directory already contains Go files, the fake joins that package. If it is empty, the fake's package is named after the directory. So to name the package differently from its directory, say impl_fakes in fakes/, add a file declaring that package first:
// fakes/doc.go
package impl_fakes//counterfeiter:generate -o fakes . MyInterface-fake-name names one fake. -fake-name-template is a Go text/template in which {{.TargetName}} is the name of the interface being faked (first letter upper-cased); it applies wherever -fake-name is not given, and can be set once for the package on the -generate line. The file name follows the fake's name.
//counterfeiter:generate -fake-name Repo . MyRepository
//counterfeiter:generate -fake-name-template '{{.TargetName}}Double' . MyPresenter$ go generate ./...
Writing `Repo` to `foofakes/repo.go`... Done
Writing `MyPresenterDouble` to `foofakes/my_presenter_double.go`... Done-header prepends the contents of a file to every generated fake, for a licence header for example. Set it once on the -generate line, or per directive.
//go:generate go tool counterfeiter -generate -header ../LICENSE.headerFunction types can be faked as well. The fake is a struct whose Spy method has the function's signature, so fake.Spy goes wherever the function is expected. Returns, CallCount, ArgsForCall and the rest work as for a method, without a method name in front.
//counterfeiter:generate . RequestHandler
type RequestHandler func(*http.Request) errorfake := &foofakes.FakeRequestHandler{}
fake.Returns(nil)
server := NewServer(fake.Spy)Package mode, -p, is for code that calls package-level functions directly, os.Hostname() say, and has no interface to fake. It writes a file with an interface whose methods are the package's exported functions, and a shim struct that forwards each method to the package. The file carries a counterfeiter:generate directive of its own, so the next go generate produces a fake of that interface.
//counterfeiter:generate -p os$ go generate ./...
Writing `Os` to `osshim/os.go`... Done
$ go generate ./...
Writing `FakeOs` to `osshimfakes/fake_os.go`... DoneYour code takes the osshim.Os interface, production code passes &osshim.OsShim{}, tests pass the fake.
A trailing - prints the fake to standard output instead of writing a file:
$ go tool counterfeiter . MySpecialInterface --q drops the Writing ... lines, which is useful in a -generate line for a package with many fakes.
Instantiate fakes:
import "my-repo/path/to/foo/foofakes"
var fake = &foofakes.FakeMySpecialInterface{}For each method `` of the interface, the fake has:
Returns, ReturnsOnCall |
stub what it returns |
Calls, Stub |
replace it with a function |
CallCount, ArgsForCall |
inspect the calls it received |
You can stub return values:
fake.DoThingsReturns(3, errors.New("the-error"))
num, err := fake.DoThings("stuff", 5)
Expect(num).To(Equal(3))
Expect(err).To(Equal(errors.New("the-error")))or stub them for one call at a time, counting from zero; calls without an entry fall back to Returns:
fake.DoThingsReturnsOnCall(0, 1, nil)
fake.DoThingsReturnsOnCall(1, 0, errors.New("the-error"))When the result depends on t
No open issues yet, or sync has not completed.