Skip to content

hoenn/mimic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

39 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mimic

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.

Usage

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

Functionality Matrix

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

Same-package generation

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.

Examples

// 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
}

About

Mimic generates lightweight fake implementations of interfaces to inject as dependencies in tests

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors