Repository: matryer/moq
Pull request #140 (2020-09-13 20:13:32)

add skip-ensure flag to avoid import cycle
For mocks generated outside of the tested package with tests lives inside the same package as the tested code (i.e. pkg_test not used) --skip-ensure suppresses import of the source pkg to avoid import cycle

re: #139



Patch:
diff --git a/README.md b/README.md
index bdf7347..f77faa5 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,9 @@ moq [flags] source-dir interface [interface2 [interface3 [...]]]
     	package name (default will infer)
   -stub
     	return zero values when no mock implementation is provided, do not panic
+  -skip-ensure
+		suppress mock implementation check, avoid import cycle if mocks generated outside of the tested package
+
 Specifying an alias for the mock is also supported with the format 'interface:alias'
 Ex: moq -pkg different . MyInterface:MyMock
 ```
diff --git a/main.go b/main.go
index d6a5c8e..25add32 100644
--- a/main.go
+++ b/main.go
@@ -17,11 +17,12 @@ import (
 var version string
 
 type userFlags struct {
-	outFile   string
-	pkgName   string
-	formatter string
-	stubImpl  bool
-	args      []string
+	outFile    string
+	pkgName    string
+	formatter  string
+	stubImpl   bool
+	skipEnsure bool
+	args       []string
 }
 
 func main() {
@@ -31,6 +32,8 @@ func main() {
 	flag.StringVar(&flags.formatter, "fmt", "", "go pretty-printer: gofmt, goimports or noop (default gofmt)")
 	flag.BoolVar(&flags.stubImpl, "stub", false,
 		"return zero values when no mock implementation is provided, do not panic")
+	flag.BoolVar(&flags.skipEnsure, "skip-ensure", false,
+		"suppress mock implementation check, avoid import cycle if mocks generated outside of the tested package")
 
 	flag.Usage = func() {
 		fmt.Println(`moq [flags] source-dir interface [interface2 [interface3 [...]]]`)
@@ -62,10 +65,11 @@ func run(flags userFlags) error {
 
 	srcDir, args := flags.args[0], flags.args[1:]
 	m, err := moq.New(moq.Config{
-		SrcDir:    srcDir,
-		PkgName:   flags.pkgName,
-		Formatter: flags.formatter,
-		StubImpl:  flags.stubImpl,
+		SrcDir:     srcDir,
+		PkgName:    flags.pkgName,
+		Formatter:  flags.formatter,
+		StubImpl:   flags.stubImpl,
+		SkipEnsure: flags.skipEnsure,
 	})
 	if err != nil {
 		return err
diff --git a/pkg/moq/moq.go b/pkg/moq/moq.go
index 1725361..cc07fd1 100644
--- a/pkg/moq/moq.go
+++ b/pkg/moq/moq.go
@@ -19,12 +19,13 @@ 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)
-	stubImpl bool
+	srcPkg     *packages.Package
+	tmpl       *template.Template
+	pkgName    string
+	pkgPath    string
+	fmter      func(src []byte) ([]byte, error)
+	stubImpl   bool
+	skipEnsure bool
 
 	imports map[string]bool
 }
@@ -32,10 +33,11 @@ type Mocker struct {
 // Config specifies details about how interfaces should be mocked.
 // SrcDir is the only field which needs be specified.
 type Config struct {
-	SrcDir    string
-	PkgName   string
-	Formatter string
-	StubImpl  bool
+	SrcDir     string
+	PkgName    string
+	Formatter  string
+	StubImpl   bool
+	SkipEnsure bool
 }
 
 // New makes a new Mocker for the specified package directory.
@@ -69,13 +71,14 @@ func New(conf Config) (*Mocker, error) {
 	}
 
 	return &Mocker{
-		tmpl:     tmpl,
-		srcPkg:   srcPkg,
-		pkgName:  pkgName,
-		pkgPath:  pkgPath,
-		fmter:    fmter,
-		stubImpl: conf.StubImpl,
-		imports:  make(map[string]bool),
+		tmpl:       tmpl,
+		srcPkg:     srcPkg,
+		pkgName:    pkgName,
+		pkgPath:    pkgPath,
+		fmter:      fmter,
+		stubImpl:   conf.StubImpl,
+		skipEnsure: conf.SkipEnsure,
+		imports:    make(map[string]bool),
 	}, nil
 }
 
@@ -114,6 +117,7 @@ func (m *Mocker) Mock(w io.Writer, names ...string) error {
 		PackageName: m.pkgName,
 		Imports:     moqImports,
 		StubImpl:    m.stubImpl,
+		SkipEnsure:  m.skipEnsure,
 	}
 
 	mocksMethods := false
@@ -254,6 +258,7 @@ type doc struct {
 	Objects             []obj
 	Imports             []string
 	StubImpl            bool
+	SkipEnsure          bool
 }
 
 type obj struct {
diff --git a/pkg/moq/template.go b/pkg/moq/template.go
index 442bdf1..fae942e 100644
--- a/pkg/moq/template.go
+++ b/pkg/moq/template.go
@@ -11,6 +11,7 @@ var moqTemplate = `// Code generated by moq; DO NOT EDIT.
 package {{.PackageName}}
 {{- $sourcePackagePrefix := .SourcePackagePrefix}}
 {{- $stubImpl := .StubImpl}}
+{{- $skipEnsure := .SkipEnsure}}
 
 import (
 {{- range .Imports }}
@@ -20,9 +21,11 @@ import (
 
 {{ range $i, $obj := .Objects -}}
 
+{{- if not $skipEnsure -}}
 // Ensure, that {{.MockName}} does implement {{$sourcePackagePrefix}}{{.InterfaceName}}.
 // If this is not the case, regenerate this file with moq.
 var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{}
+{{- end }}
 
 // {{.MockName}} is a mock implementation of {{$sourcePackagePrefix}}{{.InterfaceName}}.
 //


Test patch:
diff --git a/pkg/moq/moq_test.go b/pkg/moq/moq_test.go
index 65a0639..b3211e1 100644
--- a/pkg/moq/moq_test.go
+++ b/pkg/moq/moq_test.go
@@ -172,6 +172,33 @@ func TestMoqExplicitPackageWithStaticCheck(t *testing.T) {
 	}
 }
 
+func TestMoqSkipEnsure(t *testing.T) {
+	m, err := New(Config{SrcDir: "testpackages/example", PkgName: "different", SkipEnsure: true})
+	if err != nil {
+		t.Fatalf("moq.New: %s", err)
+	}
+	var buf bytes.Buffer
+	err = m.Mock(&buf, "PersonStore")
+	if err != nil {
+		t.Errorf("m.Mock: %s", err)
+	}
+	s := buf.String()
+	// assertions of things that should be mentioned
+	var strs = []string{
+		"package different",
+		"type PersonStoreMock struct",
+		"CreateFunc func(ctx context.Context, person *example.Person, confirm bool) error",
+		"GetFunc func(ctx context.Context, id string) (*example.Person, error)",
+		"func (mock *PersonStoreMock) Create(ctx context.Context, person *example.Person, confirm bool) error",
+		"func (mock *PersonStoreMock) Get(ctx context.Context, id string) (*example.Person, error)",
+	}
+	for _, str := range strs {
+		if !strings.Contains(s, str) {
+			t.Errorf("expected but missing: \"%s\"", str)
+		}
+	}
+}
+
 func TestNotCreatingEmptyDirWhenPkgIsGiven(t *testing.T) {
 	m, err := New(Config{SrcDir: "testpackages/example", PkgName: "different"})
 	if err != nil {
diff --git a/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go b/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go
index 0ca586f..a9440cf 100644
--- a/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go
+++ b/pkg/moq/testpackages/gogenvendoring/user/user_moq_test.go
@@ -8,10 +8,6 @@ import (
 	"sync"
 )
 
-var (
-	lockServiceMockDoSomething sync.RWMutex
-)
-
 // Ensure, that ServiceMock does implement Service.
 // If this is not the case, regenerate this file with moq.
 var _ Service = &ServiceMock{}
@@ -43,6 +39,7 @@ type ServiceMock struct {
 			In1 somerepo.SomeType
 		}
 	}
+	lockDoSomething sync.RWMutex
 }
 
 // DoSomething calls DoSomethingFunc.
@@ -55,9 +52,9 @@ func (mock *ServiceMock) DoSomething(in1 somerepo.SomeType) error {
 	}{
 		In1: in1,
 	}
-	lockServiceMockDoSomething.Lock()
+	mock.lockDoSomething.Lock()
 	mock.calls.DoSomething = append(mock.calls.DoSomething, callInfo)
-	lockServiceMockDoSomething.Unlock()
+	mock.lockDoSomething.Unlock()
 	return mock.DoSomethingFunc(in1)
 }
 
@@ -70,8 +67,8 @@ func (mock *ServiceMock) DoSomethingCalls() []struct {
 	var calls []struct {
 		In1 somerepo.SomeType
 	}
-	lockServiceMockDoSomething.RLock()
+	mock.lockDoSomething.RLock()
 	calls = mock.calls.DoSomething
-	lockServiceMockDoSomething.RUnlock()
+	mock.lockDoSomething.RUnlock()
 	return calls
 }