Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ moq [flags] source-dir interface [interface2 [interface3 [...]]]
return zero values when no mock implementation is provided, do not panic
-version
show the version for moq
-with-require-calls
generate functions to check that all non-nil mock methods were called
-with-resets
generate functions to facilitate resetting calls made to a mock

Expand Down Expand Up @@ -134,4 +136,3 @@ The Moq project (and all code) is licensed under the [MIT License](LICENSE).
Moq was created by [Mat Ryer](https://twitter.com/matryer) and [David Hernandez](https://github.com/dahernan), with ideas lovingly stolen from [Ernesto Jimenez](https://github.com/ernesto-jimenez). Featuring a major refactor by @sudo-suhas, as well as lots of other contributors.

The Moq logo was created by [Chris Ryer](http://chrisryer.co.uk) and is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/).

26 changes: 26 additions & 0 deletions internal/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,32 @@ func (mock *{{$mock.MockName}}
{{end -}}
}
{{end -}}

{{- if $.WithRequireCalls }}
// RequireCalls returns an error if any non-nil methods were not called.
// This is the inverse of "panic on nil method".
// It can be used to require that unused mocks are removed from tests.
//
// m := ExampleMock{ ExampleFunc: func(){}, OtherFunc: nil }
// someOtherFunction(m) // Doesn't call m.Example()
//
// if err := m.RequireCalls(); err != nil{
// t.Errorf("All non-nil methods must be called: %s", err)
// }
func (mock *{{$mock.MockName}}) RequireCalls() error {
{{- range .Methods}}
if mock.{{.Name}}Func != nil {
mock.lock{{.Name}}.Lock()
defer mock.lock{{.Name}}.Unlock()
if len(mock.calls.{{.Name}}) == 0 {
return errors.New("{{$mock.MockName}}.{{.Name}} is non-nil but was never called")
}
}
{{end -}}

return nil
}
{{end -}}
{{end -}}
`

Expand Down
15 changes: 8 additions & 7 deletions internal/template/template_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (

// Data is the template data used to render the Moq template.
type Data struct {
PkgName string
SrcPkgQualifier string
Imports []*registry.Package
Mocks []MockData
StubImpl bool
SkipEnsure bool
WithResets bool
PkgName string
SrcPkgQualifier string
Imports []*registry.Package
Mocks []MockData
StubImpl bool
SkipEnsure bool
WithResets bool
WithRequireCalls bool
}

// MocksSomeMethod returns true of any one of the Mocks has at least 1
Expand Down
32 changes: 18 additions & 14 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ import (
var Version string = "dev"

type userFlags struct {
outFile string
pkgName string
formatter string
stubImpl bool
skipEnsure bool
withResets bool
remove bool
args []string
outFile string
pkgName string
formatter string
stubImpl bool
skipEnsure bool
withResets bool
withRequireCalls bool
remove bool
args []string
}

func main() {
Expand All @@ -39,6 +40,8 @@ func main() {
flag.BoolVar(&flags.remove, "rm", false, "first remove output file, if it exists")
flag.BoolVar(&flags.withResets, "with-resets", false,
"generate functions to facilitate resetting calls made to a mock")
flag.BoolVar(&flags.withRequireCalls, "with-require-calls", false,
"generate functions to check that all non-nil mock methods were called")

flag.Usage = func() {
fmt.Println(`moq [flags] source-dir interface [interface2 [interface3 [...]]]`)
Expand Down Expand Up @@ -83,12 +86,13 @@ 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,
SkipEnsure: flags.skipEnsure,
WithResets: flags.withResets,
SrcDir: srcDir,
PkgName: flags.pkgName,
Formatter: flags.formatter,
StubImpl: flags.stubImpl,
SkipEnsure: flags.skipEnsure,
WithResets: flags.withResets,
WithRequireCalls: flags.withRequireCalls,
})
if err != nil {
return err
Expand Down
28 changes: 17 additions & 11 deletions pkg/moq/moq.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ 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
SkipEnsure bool
WithResets bool
SrcDir string
PkgName string
Formatter string
StubImpl bool
SkipEnsure bool
WithResets bool
WithRequireCalls bool
}

// New makes a new Mocker for the specified package directory.
Expand Down Expand Up @@ -78,11 +79,16 @@ func (m *Mocker) Mock(w io.Writer, namePairs ...string) error {
}

data := template.Data{
PkgName: m.mockPkgName(),
Mocks: mocks,
StubImpl: m.cfg.StubImpl,
SkipEnsure: m.cfg.SkipEnsure,
WithResets: m.cfg.WithResets,
PkgName: m.mockPkgName(),
Mocks: mocks,
StubImpl: m.cfg.StubImpl,
SkipEnsure: m.cfg.SkipEnsure,
WithResets: m.cfg.WithResets,
WithRequireCalls: m.cfg.WithRequireCalls,
}

if data.WithRequireCalls {
m.registry.AddImport(types.NewPackage("errors", "errors"))
}

if data.MocksSomeMethod() {
Expand Down
6 changes: 6 additions & 0 deletions pkg/moq/moq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,12 @@ func TestMockGolden(t *testing.T) {
interfaces: []string{"ResetStore", "ResetStoreGeneric"},
goldenFile: filepath.Join("testpackages/withresets", "withresets_moq.golden.go"),
},
{
name: "WithRequireCalls",
cfg: Config{SrcDir: "testpackages/withrequirecalls", WithRequireCalls: true},
interfaces: []string{"RequireCalls"},
goldenFile: filepath.Join("testpackages/withrequirecalls", "withrequirecalls_moq.golden.go"),
},
{
name: "RangeNumber",
cfg: Config{SrcDir: "testpackages/rangenum"},
Expand Down
6 changes: 6 additions & 0 deletions pkg/moq/testpackages/withrequirecalls/withrequirecalls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package withrequirecalls

type RequireCalls interface {
GetExample()
SetExample()
}
134 changes: 134 additions & 0 deletions pkg/moq/testpackages/withrequirecalls/withrequirecalls_moq.golden.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 43 additions & 0 deletions pkg/moq/testpackages/withrequirecalls/withrequirecalls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package withrequirecalls

import (
"strings"
"testing"
)

func TestRequireCalls(t *testing.T) {
r := RequireCallsMock{}

// no funcs configured, expect no error
err := r.RequireCalls()
if err != nil {
t.Errorf("All methods are nil, but got error: %s", err)
}

// configure both funcs
r.GetExampleFunc = func() {}
r.SetExampleFunc = func() {}

// call only one of them
r.GetExample()

// expect error
err = r.RequireCalls()
if err == nil {
t.Fatalf("Expected error, since not all methods have been called")
}

// expect error to contain SetExample
if expected := "RequireCallsMock.SetExample is non-nil but was never called"; !strings.Contains(err.Error(), expected) {
t.Errorf("Unexpected error message:\n\tExpected: %s\n\tGot: %s", expected, err)
}

// call SetExample
r.SetExample()

// all configured funcs have been called, no error
err = r.RequireCalls()
if err != nil {
t.Errorf("All methods should have been called, but got error: %s", err)
}
}