mimic is a tool for generating stubbed implementations, or fakes, of interfaces to inject as dependencies during tests that are as light as possible. Other tools like mockgen and counterfeiter bring in a ton of functionality that is excessive for some projects and make much larger generated fakes. mimic generates the bare minimum.
With this simplistic implementation you can recreate missing features like call counts, thread safety, returns for call, etc., in each stub as needed.
Use with a go:generate directive:
package foo
//go:generate go run github.com/hoenn/mimic InterfaceA,InterfaceB
type InterfaceA interface {}
// ...
type InterfaceB interface {}You can also specify the file the interface appears in in the generate directive, which can be useful if you want a single generate.go file.
package foo
// Generates fileA_fake.go and fileB_fake.go
//go:generate go run github.com/hoenn/mimic ./fileA.go InterfaceA,InterfaceB
//go:generate go run github.com/hoenn/mimic ./fileB.go InterfaceC| Feature | Status | Notes |
|---|---|---|
| Basic methods | Yes | Named params, results, zero values |
| Unnamed params | Yes | Auto-named arg0, arg1, ... |
| Variadic params | Yes | Forwarded with ... |
| Empty interfaces | Yes | Generates empty struct |
| All builtin types | Yes | Correct zero values |
| Pointer/slice/map/chan/func returns | Yes | Returns nil |
| Array returns | Yes | Returns [N]T{} |
interface{}/any returns |
Yes | Returns nil |
| Named struct returns | Yes | Returns T{} |
pkg.Type returns |
Yes | Returns pkg.T{} |
| Multiple interfaces per file | Yes | Comma-separated names |
| Same-file embedded interfaces | Yes | Recursive resolution |
| Transitive embeds (A→B→C) | Yes | Recursive |
| Mixed embeds + methods | Yes | Both collected |
Cross-package interface and parameter embeds (io.Reader) |
Yes | Handled |
| Compile time interface assertions | Yes* | Leaves a comment instead for unsupported complex type constraints |
Generic interfaces (T[K]) |
Yes | Type params + constraints preserved |
| Diamond embedding dedup | No | Will generate duplicate fields |
| Overlapping method dedup (Go 1.14+) | No | Same issue |
Type constraint unions (~int | string) |
No | Not handled |
mimic generates fakes into the same package as the interface definition. This is a deliberate design choice as same-package generation has full visibility into unexported types and avoids circular dependencies. Interfaces with unexported types in their method signatures work without any additional configuration. For example:
type message string
type Client interface {
Send(msg *message) error // unexported type
}Other tools that generate fakes into separate packages cannot compile fakes for an interface like this, as the unexported type will be unavailable in the fakefoo package.
The tradeoff for this functionality is that generated fakes are compiled with production code. In practice this should have minimal impact since the generated fake is a lightweight struct.
// testdata.go where an interface is defined.
package testdata
type Job struct {
name string
}
type JobQueuer interface {
Enqueue(j *Job) error
Dequeue(j *Job) error
Queue() []*Job
}
// testdata_fake.go where the fake is generated.
// Code generated by mimic; DO NOT EDIT.
package testdata
// FakeJobQueuer implements JobQueuer.
type FakeJobQueuer struct {
EnqueueStub func(j *Job) error
DequeueStub func(j *Job) error
QueueStub func() []*Job
}
var _ JobQueuer = (*FakeJobQueuer)(nil)
func (fakeImpl *FakeJobQueuer) Enqueue(j *Job) error {
if fakeImpl.EnqueueStub != nil {
return fakeImpl.EnqueueStub(j)
}
return nil
}
func (fakeImpl *FakeJobQueuer) Dequeue(j *Job) error {
if fakeImpl.DequeueStub != nil {
return fakeImpl.DequeueStub(j)
}
return nil
}
func (fakeImpl *FakeJobQueuer) Queue() []*Job {
if fakeImpl.QueueStub != nil {
return fakeImpl.QueueStub()
}
return nil
}
// testdata_test.go where we inject our fake for testing and
// define the functions to assert behaviors of the dependent.
func TestJobQueuer(t *testing.T) {
f := &FakeJobQueuer{
EnqueueStub: func(j *Job) error {
// Implement behavior you want to fake, like returning
// a specific error.
},
DequeueStub: func(j *Job) error {
// Or use a bool to check if a function is ever called.
// dequeueCalled = true
// dequeueCalls++
},
// Undefined QueueStub() returns zero value when called.
}
svc := newComplicatedService(f)
// Expected dequeueCalled to be true
// Expected some error to happen when queueing
}