diff --git a/main.go b/main.go index e13533f..9b2b4ca 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ type userFlags struct { outFile string pkgName string formatter string + stubImpl bool args []string } @@ -25,6 +26,8 @@ func main() { flag.StringVar(&flags.outFile, "out", "", "output file (default stdout)") flag.StringVar(&flags.pkgName, "pkg", "", "package name (default will infer)") flag.StringVar(&flags.formatter, "fmt", "", "go pretty-printer: gofmt (default) or goimports") + flag.BoolVar(&flags.stubImpl, "stubImpl", false, + "return zero values when no mock implementation is provided, do not panic") flag.Usage = func() { fmt.Println(`moq [flags] source-dir interface [interface2 [interface3 [...]]]`) @@ -59,6 +62,7 @@ func run(flags userFlags) error { SrcDir: srcDir, PkgName: flags.pkgName, Formatter: flags.formatter, + StubImpl: flags.stubImpl, }) if err != nil { return err diff --git a/pkg/moq/moq.go b/pkg/moq/moq.go index 0a10202..c3b0fa0 100644 --- a/pkg/moq/moq.go +++ b/pkg/moq/moq.go @@ -19,11 +19,12 @@ import ( // Mocker can generate mock structs. type Mocker struct { - srcPkg *packages.Package - tmpl *template.Template - pkgName string - pkgPath string - fmter func(src []byte) ([]byte, error) + srcPkg *packages.Package + tmpl *template.Template + pkgName string + pkgPath string + fmter func(src []byte) ([]byte, error) + stubImpl bool imports map[string]bool } @@ -34,6 +35,7 @@ type Config struct { SrcDir string PkgName string Formatter string + StubImpl bool } // New makes a new Mocker for the specified package directory. @@ -64,12 +66,13 @@ func New(conf Config) (*Mocker, error) { } return &Mocker{ - tmpl: tmpl, - srcPkg: srcPkg, - pkgName: pkgName, - pkgPath: pkgPath, - fmter: fmter, - imports: make(map[string]bool), + tmpl: tmpl, + srcPkg: srcPkg, + pkgName: pkgName, + pkgPath: pkgPath, + fmter: fmter, + stubImpl: conf.StubImpl, + imports: make(map[string]bool), }, nil } @@ -107,6 +110,7 @@ func (m *Mocker) Mock(w io.Writer, names ...string) error { doc := doc{ PackageName: m.pkgName, Imports: moqImports, + StubImpl: m.stubImpl, } mocksMethods := false @@ -246,6 +250,7 @@ type doc struct { SourcePackagePrefix string Objects []obj Imports []string + StubImpl bool } type obj struct { @@ -275,7 +280,7 @@ func (m *method) ArgCallList() string { return strings.Join(params, ", ") } -func (m *method) ReturnArglist() string { +func (m *method) ReturnArgTypeList() string { params := make([]string, len(m.Returns)) for i, p := range m.Returns { params[i] = p.TypeString() @@ -286,6 +291,14 @@ func (m *method) ReturnArglist() string { return strings.Join(params, ", ") } +func (m *method) ReturnArgNameList() string { + params := make([]string, len(m.Returns)) + for i, p := range m.Returns { + params[i] = p.Name + } + return strings.Join(params, ", ") +} + type param struct { Name string Type string diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go index 2178e62..1a490d6 100644 --- a/pkg/moq/moq_test.go +++ b/pkg/moq/moq_test.go @@ -284,23 +284,19 @@ func TestNothingToReturn(t *testing.T) { } func TestChannelNames(t *testing.T) { - m, err := New(Config{SrcDir: "testpackages/channels"}) + m, err := New(Config{SrcDir: "testpackages/channels", StubImpl: true}) if err != nil { t.Fatalf("moq.New: %s", err) } + var buf bytes.Buffer - err = m.Mock(&buf, "Queuer") - if err != nil { + if err = m.Mock(&buf, "Queuer"); err != nil { t.Errorf("m.Mock: %s", err) } - s := buf.String() - var strs = []string{ - "func (mock *QueuerMock) Sub(topic string) (<-chan Queue, error)", - } - for _, str := range strs { - if !strings.Contains(s, str) { - t.Errorf("expected but missing: \"%s\"", str) - } + + golden := filepath.Join("testpackages/channels", "queuer_moq.golden.go") + if err := matchGoldenFile(golden, buf.Bytes()); err != nil { + t.Errorf("check golden file: %s", err) } } diff --git a/pkg/moq/template.go b/pkg/moq/template.go index 7eb9fdd..442bdf1 100644 --- a/pkg/moq/template.go +++ b/pkg/moq/template.go @@ -10,6 +10,7 @@ var moqTemplate = `// Code generated by moq; DO NOT EDIT. package {{.PackageName}} {{- $sourcePackagePrefix := .SourcePackagePrefix}} +{{- $stubImpl := .StubImpl}} import ( {{- range .Imports }} @@ -29,7 +30,7 @@ var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{} // // // make and configure a mocked {{$sourcePackagePrefix}}{{.InterfaceName}} // mocked{{.InterfaceName}} := &{{.MockName}}{ {{ range .Methods }} -// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArglist}} { +// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArgTypeList}} { // panic("mock out the {{.Name}} method") // },{{- end }} // } @@ -41,7 +42,7 @@ var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{} type {{.MockName}} struct { {{- range .Methods }} // {{.Name}}Func mocks the {{.Name}} method. - {{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}} + {{.Name}}Func func({{ .Arglist }}) {{.ReturnArgTypeList}} {{ end }} // calls tracks calls to the methods. calls struct { @@ -61,10 +62,12 @@ type {{.MockName}} struct { } {{ range .Methods }} // {{.Name}} calls {{.Name}}Func. -func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} { +func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArgTypeList}} { +{{- if not $stubImpl }} if mock.{{.Name}}Func == nil { panic("{{$obj.MockName}}.{{.Name}}Func: method is nil but {{$obj.InterfaceName}}.{{.Name}} was just called") } +{{- end }} callInfo := struct { {{- range .Params }} {{ .Name | Exported }} {{ .Type }} @@ -77,9 +80,24 @@ func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} { mock.lock{{.Name}}.Lock() mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) mock.lock{{.Name}}.Unlock() -{{- if .ReturnArglist }} +{{- if .Returns }} + {{- if $stubImpl }} + if mock.{{.Name}}Func == nil { + var ( + {{- range .Returns }} + {{.Name}} {{.Type}} + {{- end }} + ) + return {{.ReturnArgNameList}} + } + {{- end }} return mock.{{.Name}}Func({{.ArgCallList}}) {{- else }} + {{- if $stubImpl }} + if mock.{{.Name}}Func == nil { + return + } + {{- end }} mock.{{.Name}}Func({{.ArgCallList}}) {{- end }} } diff --git a/pkg/moq/testpackages/channels/example.go b/pkg/moq/testpackages/channels/example.go index cef8ce8..13a0445 100644 --- a/pkg/moq/testpackages/channels/example.go +++ b/pkg/moq/testpackages/channels/example.go @@ -6,4 +6,5 @@ type Queue []string // Queuer provides a channel example. type Queuer interface { Sub(topic string) (<-chan Queue, error) + Unsub(topic string) } diff --git a/pkg/moq/testpackages/channels/queuer_moq.golden.go b/pkg/moq/testpackages/channels/queuer_moq.golden.go new file mode 100644 index 0000000..4d71ec6 --- /dev/null +++ b/pkg/moq/testpackages/channels/queuer_moq.golden.go @@ -0,0 +1,120 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package channels + +import ( + "sync" +) + +// Ensure, that QueuerMock does implement Queuer. +// If this is not the case, regenerate this file with moq. +var _ Queuer = &QueuerMock{} + +// QueuerMock is a mock implementation of Queuer. +// +// func TestSomethingThatUsesQueuer(t *testing.T) { +// +// // make and configure a mocked Queuer +// mockedQueuer := &QueuerMock{ +// SubFunc: func(topic string) (<-chan Queue, error) { +// panic("mock out the Sub method") +// }, +// UnsubFunc: func(topic string) { +// panic("mock out the Unsub method") +// }, +// } +// +// // use mockedQueuer in code that requires Queuer +// // and then make assertions. +// +// } +type QueuerMock struct { + // SubFunc mocks the Sub method. + SubFunc func(topic string) (<-chan Queue, error) + + // UnsubFunc mocks the Unsub method. + UnsubFunc func(topic string) + + // calls tracks calls to the methods. + calls struct { + // Sub holds details about calls to the Sub method. + Sub []struct { + // Topic is the topic argument value. + Topic string + } + // Unsub holds details about calls to the Unsub method. + Unsub []struct { + // Topic is the topic argument value. + Topic string + } + } + lockSub sync.RWMutex + lockUnsub sync.RWMutex +} + +// Sub calls SubFunc. +func (mock *QueuerMock) Sub(topic string) (<-chan Queue, error) { + callInfo := struct { + Topic string + }{ + Topic: topic, + } + mock.lockSub.Lock() + mock.calls.Sub = append(mock.calls.Sub, callInfo) + mock.lockSub.Unlock() + if mock.SubFunc == nil { + var ( + out1 <-chan Queue + out2 error + ) + return out1, out2 + } + return mock.SubFunc(topic) +} + +// SubCalls gets all the calls that were made to Sub. +// Check the length with: +// len(mockedQueuer.SubCalls()) +func (mock *QueuerMock) SubCalls() []struct { + Topic string +} { + var calls []struct { + Topic string + } + mock.lockSub.RLock() + calls = mock.calls.Sub + mock.lockSub.RUnlock() + return calls +} + +// Unsub calls UnsubFunc. +func (mock *QueuerMock) Unsub(topic string) { + callInfo := struct { + Topic string + }{ + Topic: topic, + } + mock.lockUnsub.Lock() + mock.calls.Unsub = append(mock.calls.Unsub, callInfo) + mock.lockUnsub.Unlock() + if mock.UnsubFunc == nil { + return + } + mock.UnsubFunc(topic) +} + +// UnsubCalls gets all the calls that were made to Unsub. +// Check the length with: +// len(mockedQueuer.UnsubCalls()) +func (mock *QueuerMock) UnsubCalls() []struct { + Topic string +} { + var calls []struct { + Topic string + } + mock.lockUnsub.RLock() + calls = mock.calls.Unsub + mock.lockUnsub.RUnlock() + return calls +}