Add -stub flag to stub func implementation (#132)

When a mock is generated with the flag enabled, it introduces the
following changes:
- Does not panic on calling the method without a mock implementation.
- Return zero values iff the implementation is not provided and the
  method has return parameters.

Co-authored-by: Scott Leuthaeuser <scott_leuthaeuser@homedepot.com>
This commit is contained in:
Suhas Karanth 2020-08-16 12:24:12 +05:30 committed by GitHub
parent 24883c20a6
commit 83ab8dd79f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 179 additions and 27 deletions

View File

@ -17,6 +17,7 @@ type userFlags struct {
outFile string outFile string
pkgName string pkgName string
formatter string formatter string
stubImpl bool
args []string args []string
} }
@ -25,6 +26,8 @@ func main() {
flag.StringVar(&flags.outFile, "out", "", "output file (default stdout)") flag.StringVar(&flags.outFile, "out", "", "output file (default stdout)")
flag.StringVar(&flags.pkgName, "pkg", "", "package name (default will infer)") flag.StringVar(&flags.pkgName, "pkg", "", "package name (default will infer)")
flag.StringVar(&flags.formatter, "fmt", "", "go pretty-printer: gofmt (default) or goimports") 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() { flag.Usage = func() {
fmt.Println(`moq [flags] source-dir interface [interface2 [interface3 [...]]]`) fmt.Println(`moq [flags] source-dir interface [interface2 [interface3 [...]]]`)
@ -59,6 +62,7 @@ func run(flags userFlags) error {
SrcDir: srcDir, SrcDir: srcDir,
PkgName: flags.pkgName, PkgName: flags.pkgName,
Formatter: flags.formatter, Formatter: flags.formatter,
StubImpl: flags.stubImpl,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -24,6 +24,7 @@ type Mocker struct {
pkgName string pkgName string
pkgPath string pkgPath string
fmter func(src []byte) ([]byte, error) fmter func(src []byte) ([]byte, error)
stubImpl bool
imports map[string]bool imports map[string]bool
} }
@ -34,6 +35,7 @@ type Config struct {
SrcDir string SrcDir string
PkgName string PkgName string
Formatter string Formatter string
StubImpl bool
} }
// New makes a new Mocker for the specified package directory. // New makes a new Mocker for the specified package directory.
@ -69,6 +71,7 @@ func New(conf Config) (*Mocker, error) {
pkgName: pkgName, pkgName: pkgName,
pkgPath: pkgPath, pkgPath: pkgPath,
fmter: fmter, fmter: fmter,
stubImpl: conf.StubImpl,
imports: make(map[string]bool), imports: make(map[string]bool),
}, nil }, nil
} }
@ -107,6 +110,7 @@ func (m *Mocker) Mock(w io.Writer, names ...string) error {
doc := doc{ doc := doc{
PackageName: m.pkgName, PackageName: m.pkgName,
Imports: moqImports, Imports: moqImports,
StubImpl: m.stubImpl,
} }
mocksMethods := false mocksMethods := false
@ -246,6 +250,7 @@ type doc struct {
SourcePackagePrefix string SourcePackagePrefix string
Objects []obj Objects []obj
Imports []string Imports []string
StubImpl bool
} }
type obj struct { type obj struct {
@ -275,7 +280,7 @@ func (m *method) ArgCallList() string {
return strings.Join(params, ", ") return strings.Join(params, ", ")
} }
func (m *method) ReturnArglist() string { func (m *method) ReturnArgTypeList() string {
params := make([]string, len(m.Returns)) params := make([]string, len(m.Returns))
for i, p := range m.Returns { for i, p := range m.Returns {
params[i] = p.TypeString() params[i] = p.TypeString()
@ -286,6 +291,14 @@ func (m *method) ReturnArglist() string {
return strings.Join(params, ", ") 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 { type param struct {
Name string Name string
Type string Type string

View File

@ -284,23 +284,19 @@ func TestNothingToReturn(t *testing.T) {
} }
func TestChannelNames(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 { if err != nil {
t.Fatalf("moq.New: %s", err) t.Fatalf("moq.New: %s", err)
} }
var buf bytes.Buffer var buf bytes.Buffer
err = m.Mock(&buf, "Queuer") if err = m.Mock(&buf, "Queuer"); err != nil {
if err != nil {
t.Errorf("m.Mock: %s", err) t.Errorf("m.Mock: %s", err)
} }
s := buf.String()
var strs = []string{ golden := filepath.Join("testpackages/channels", "queuer_moq.golden.go")
"func (mock *QueuerMock) Sub(topic string) (<-chan Queue, error)", if err := matchGoldenFile(golden, buf.Bytes()); err != nil {
} t.Errorf("check golden file: %s", err)
for _, str := range strs {
if !strings.Contains(s, str) {
t.Errorf("expected but missing: \"%s\"", str)
}
} }
} }

View File

@ -10,6 +10,7 @@ var moqTemplate = `// Code generated by moq; DO NOT EDIT.
package {{.PackageName}} package {{.PackageName}}
{{- $sourcePackagePrefix := .SourcePackagePrefix}} {{- $sourcePackagePrefix := .SourcePackagePrefix}}
{{- $stubImpl := .StubImpl}}
import ( import (
{{- range .Imports }} {{- range .Imports }}
@ -29,7 +30,7 @@ var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{}
// //
// // make and configure a mocked {{$sourcePackagePrefix}}{{.InterfaceName}} // // make and configure a mocked {{$sourcePackagePrefix}}{{.InterfaceName}}
// mocked{{.InterfaceName}} := &{{.MockName}}{ {{ range .Methods }} // mocked{{.InterfaceName}} := &{{.MockName}}{ {{ range .Methods }}
// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArglist}} { // {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArgTypeList}} {
// panic("mock out the {{.Name}} method") // panic("mock out the {{.Name}} method")
// },{{- end }} // },{{- end }}
// } // }
@ -41,7 +42,7 @@ var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{}
type {{.MockName}} struct { type {{.MockName}} struct {
{{- range .Methods }} {{- range .Methods }}
// {{.Name}}Func mocks the {{.Name}} method. // {{.Name}}Func mocks the {{.Name}} method.
{{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}} {{.Name}}Func func({{ .Arglist }}) {{.ReturnArgTypeList}}
{{ end }} {{ end }}
// calls tracks calls to the methods. // calls tracks calls to the methods.
calls struct { calls struct {
@ -61,10 +62,12 @@ type {{.MockName}} struct {
} }
{{ range .Methods }} {{ range .Methods }}
// {{.Name}} calls {{.Name}}Func. // {{.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 { if mock.{{.Name}}Func == nil {
panic("{{$obj.MockName}}.{{.Name}}Func: method is nil but {{$obj.InterfaceName}}.{{.Name}} was just called") panic("{{$obj.MockName}}.{{.Name}}Func: method is nil but {{$obj.InterfaceName}}.{{.Name}} was just called")
} }
{{- end }}
callInfo := struct { callInfo := struct {
{{- range .Params }} {{- range .Params }}
{{ .Name | Exported }} {{ .Type }} {{ .Name | Exported }} {{ .Type }}
@ -77,9 +80,24 @@ func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} {
mock.lock{{.Name}}.Lock() mock.lock{{.Name}}.Lock()
mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo)
mock.lock{{.Name}}.Unlock() 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}}) return mock.{{.Name}}Func({{.ArgCallList}})
{{- else }} {{- else }}
{{- if $stubImpl }}
if mock.{{.Name}}Func == nil {
return
}
{{- end }}
mock.{{.Name}}Func({{.ArgCallList}}) mock.{{.Name}}Func({{.ArgCallList}})
{{- end }} {{- end }}
} }

View File

@ -6,4 +6,5 @@ type Queue []string
// Queuer provides a channel example. // Queuer provides a channel example.
type Queuer interface { type Queuer interface {
Sub(topic string) (<-chan Queue, error) Sub(topic string) (<-chan Queue, error)
Unsub(topic string)
} }

View File

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