Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add github.com/matryer/moq style mocks into mockery #725

Open
wants to merge 54 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
e06c157
Internal registry for disambiguated imports, vars (#141)
sudo-suhas Feb 1, 2021
36a85ce
feat: support generic interface generation (#175)
cgorenflo Oct 1, 2022
d1b2fd4
Go version independent moq output with consistent whitespace
sudo-suhas Nov 13, 2022
d411b15
Add flag to enable mock reset methods (#181)
cbaker Mar 8, 2023
0028a34
updates
LandonTClipp Oct 19, 2023
d16ef1b
Internal registry for disambiguated imports, vars (#141)
sudo-suhas Feb 1, 2021
5ea7cda
Fix var name generation to avoid conflict (#145)
sudo-suhas Feb 14, 2021
2c9d05c
Ignore anonymous imports when resolving import aliases (#150)
maneac Jul 4, 2021
9e0e5d0
Fix issue with custom types shadowing basic types (#163)
dedalusj Dec 18, 2021
92a6e93
Added packages.NeedDeps bit to packages.LoadMode to fix error (#171)
metalrex100 Mar 29, 2022
4468afa
feat: support generic interface generation (#175)
cgorenflo Oct 1, 2022
fb12cf9
Recursively check unique pkg names against existing imports
sudo-suhas Nov 13, 2022
ebe7652
Add imports for Named Type's type arguments (#199)
TimVosch Jun 21, 2023
222a8a9
Do not load unnecessary package information (#203)
samherrmann Aug 13, 2023
1d04e7d
Add registry package from moq
LandonTClipp Oct 19, 2023
e68ee26
updates
LandonTClipp Oct 19, 2023
3eb17fe
updates
LandonTClipp Oct 19, 2023
860c2c8
updates
LandonTClipp Oct 20, 2023
d66be2c
Add more code to plumb through all the values needed by moq registry …
LandonTClipp Nov 20, 2023
1edd82f
Successfully created first moq
LandonTClipp Dec 23, 2023
f19f1f1
add config for moq
LandonTClipp Dec 23, 2023
852b19e
add formatter back
LandonTClipp Dec 23, 2023
9d89443
fix
LandonTClipp Dec 23, 2023
9aa5b53
change moq.templ comment
LandonTClipp Feb 12, 2024
1f078dc
Merge branch 'master' into moq
LandonTClipp Feb 12, 2024
e3fe47c
fix formatting issue with master merge
LandonTClipp Feb 12, 2024
62ef480
Add generics plumbing through moq
LandonTClipp Feb 12, 2024
5d37c18
update mocks with new features
LandonTClipp Feb 12, 2024
f9bee27
Move generator to separate package
LandonTClipp Feb 13, 2024
2ded465
Add moq configuration to Taskfile
LandonTClipp Feb 13, 2024
f144fb5
fix config
LandonTClipp Feb 13, 2024
4a1905b
Add mocks for moq
LandonTClipp Feb 13, 2024
d25b5da
fix config
LandonTClipp Feb 13, 2024
63653e0
fix taskfile
LandonTClipp Feb 13, 2024
a0f012b
Split out warn logs to logging package
LandonTClipp Feb 13, 2024
19b9718
Add template-map config param.
LandonTClipp Feb 13, 2024
db5f5a6
check err of generateMockery
LandonTClipp Feb 13, 2024
05f2c4d
update moq mocks
LandonTClipp Feb 13, 2024
587f197
update docs and moq config
LandonTClipp Feb 13, 2024
1805cf2
fix tests
LandonTClipp Feb 13, 2024
0aa59fb
Merge branch 'master' into moq
LandonTClipp Feb 13, 2024
cdd540b
Fix issue with ordering of filesystem walk
LandonTClipp Feb 13, 2024
ddf4a43
Merge branch 'moq' of github.com:LandonTClipp/mockery into moq
LandonTClipp Feb 13, 2024
7f4d02d
fix to go.mod logic
LandonTClipp Feb 13, 2024
78eb414
Add context.Context to more moq functions for logging
LandonTClipp Feb 21, 2024
f0de03c
Change name of moq mocks
LandonTClipp Feb 21, 2024
a82b265
Fix moq generation when inpackage=True
LandonTClipp Feb 21, 2024
a9aac88
Fix issue with registry not importing original package
LandonTClipp Feb 21, 2024
f57f7b1
update moqs
LandonTClipp Feb 21, 2024
dd61c0f
Merge branch 'master' into moq
LandonTClipp Feb 21, 2024
c8a2304
change codecov threshold to 0
LandonTClipp Feb 21, 2024
37562c5
updates
LandonTClipp Feb 21, 2024
4ddd30b
reduce codecov patch requirement to 0
LandonTClipp Feb 21, 2024
8ff0a85
fix codecov project target
LandonTClipp Feb 21, 2024
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
Next Next commit
Internal registry for disambiguated imports, vars (#141)
* Internal registry for disambiguated imports, vars
- Move functionality in the moq package partially into
  internal/{registry,template}.
- Leverage registry to assign unique package and variable/method
  parameter names. Use import aliases if present in interface source
  package.
BREAKING CHANGE: When the interface definition does not mention the
parameter names, the field names in call info anonymous struct will be
different.
The new field names are generated using the type info (string -> s,
int -> n, chan int -> intCh, []MyType -> myTypes, map[string]int ->
stringToInt etc.).
For example, for a string parameter previously if the field name was
'In1', the new field could be 'S' or 'S1' (depends on number of
string method parameters).
* Refactor golden file tests to be table-driven
* Fix sync pkg alias handling for moq generation
* Improve, add tests (increase coverage)
* Use $.Foo in template, avoid declaring variables
$ is set to the data argument passed to Execute, that is, to the
starting value of dot.
Variables were declared to be able to refer to the parent context.
* Consistent template field formatting
* Use tabs in generated Godoc comments' example code
* Minor simplification
* go generate
* Fix conflict for generated param name of pointer type

Excellent work by @sudo-suhas.
  • Loading branch information
sudo-suhas authored and LandonTClipp committed Dec 23, 2023
commit e06c1576cf470233230245421681a1d16d3a2b22
190 changes: 190 additions & 0 deletions internal/template/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package template

import (
"io"
"strings"
"text/template"

"github.com/matryer/moq/internal/registry"
)

// Template is the Moq template. It is capable of generating the Moq
// implementation for the given template.Data.
type Template struct {
tmpl *template.Template
}

// New returns a new instance of Template.
func New() (Template, error) {
tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(moqTemplate)
if err != nil {
return Template{}, err
}

return Template{tmpl: tmpl}, nil
}

// Execute generates and writes the Moq implementation for the given
// data.
func (t Template) Execute(w io.Writer, data Data) error {
return t.tmpl.Execute(w, data)
}

// moqTemplate is the template for mocked code.
// language=GoTemplate
var moqTemplate = `// Code generated by moq; DO NOT EDIT.
// github.com/matryer/moq

package {{.PkgName}}

import (
{{- range .Imports}}
{{. | ImportStatement}}
{{- end}}
)

{{range $i, $mock := .Mocks -}}

{{- if not $.SkipEnsure -}}
// Ensure, that {{.MockName}} does implement {{$.SrcPkgQualifier}}{{.InterfaceName}}.
// If this is not the case, regenerate this file with moq.
var _ {{$.SrcPkgQualifier}}{{.InterfaceName}} = &{{.MockName}}{}
{{- end}}

// {{.MockName}} is a mock implementation of {{$.SrcPkgQualifier}}{{.InterfaceName}}.
//
// func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) {
//
// // make and configure a mocked {{$.SrcPkgQualifier}}{{.InterfaceName}}
// mocked{{.InterfaceName}} := &{{.MockName}}{
{{- range .Methods}}
// {{.Name}}Func: func({{.ArgList}}) {{.ReturnArgTypeList}} {
// panic("mock out the {{.Name}} method")
// },
{{- end}}
// }
//
// // use mocked{{.InterfaceName}} in code that requires {{$.SrcPkgQualifier}}{{.InterfaceName}}
// // and then make assertions.
//
// }
type {{.MockName}} struct {
{{- range .Methods}}
// {{.Name}}Func mocks the {{.Name}} method.
{{.Name}}Func func({{.ArgList}}) {{.ReturnArgTypeList}}
{{end}}
// calls tracks calls to the methods.
calls struct {
{{- range .Methods}}
// {{.Name}} holds details about calls to the {{.Name}} method.
{{.Name}} []struct {
{{- range .Params}}
// {{.Name | Exported}} is the {{.Name}} argument value.
{{.Name | Exported}} {{.TypeString}}
{{- end}}
}
{{- end}}
}
{{- range .Methods}}
lock{{.Name}} {{$.Imports | SyncPkgQualifier}}.RWMutex
{{- end}}
}
{{range .Methods}}
// {{.Name}} calls {{.Name}}Func.
func (mock *{{$mock.MockName}}) {{.Name}}({{.ArgList}}) {{.ReturnArgTypeList}} {
{{- if not $.StubImpl}}
if mock.{{.Name}}Func == nil {
panic("{{$mock.MockName}}.{{.Name}}Func: method is nil but {{$mock.InterfaceName}}.{{.Name}} was just called")
}
{{- end}}
callInfo := struct {
{{- range .Params}}
{{.Name | Exported}} {{.TypeString}}
{{- end}}
}{
{{- range .Params}}
{{.Name | Exported}}: {{.Name}},
{{- end}}
}
mock.lock{{.Name}}.Lock()
mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo)
mock.lock{{.Name}}.Unlock()
{{- if .Returns}}
{{- if $.StubImpl}}
if mock.{{.Name}}Func == nil {
var (
{{- range .Returns}}
{{.Name}} {{.TypeString}}
{{- end}}
)
return {{.ReturnArgNameList}}
}
{{- end}}
return mock.{{.Name}}Func({{.ArgCallList}})
{{- else}}
{{- if $.StubImpl}}
if mock.{{.Name}}Func == nil {
return
}
{{- end}}
mock.{{.Name}}Func({{.ArgCallList}})
{{- end}}
}

// {{.Name}}Calls gets all the calls that were made to {{.Name}}.
// Check the length with:
// len(mocked{{$mock.InterfaceName}}.{{.Name}}Calls())
func (mock *{{$mock.MockName}}) {{.Name}}Calls() []struct {
{{- range .Params}}
{{.Name | Exported}} {{.TypeString}}
{{- end}}
} {
var calls []struct {
{{- range .Params}}
{{.Name | Exported}} {{.TypeString}}
{{- end}}
}
mock.lock{{.Name}}.RLock()
calls = mock.calls.{{.Name}}
mock.lock{{.Name}}.RUnlock()
return calls
}
{{end -}}
{{end -}}`

// This list comes from the golint codebase. Golint will complain about any of
// these being mixed-case, like "Id" instead of "ID".
var golintInitialisms = []string{
"ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "LHS",
"QPS", "RAM", "RHS", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "UID", "UUID", "URI",
"URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS",
}

var templateFuncs = template.FuncMap{
"ImportStatement": func(imprt *registry.Package) string {
if imprt.Alias == "" {
return `"` + imprt.Path() + `"`
}
return imprt.Alias + ` "` + imprt.Path() + `"`
},
"SyncPkgQualifier": func(imports []*registry.Package) string {
for _, imprt := range imports {
if imprt.Path() == "sync" {
return imprt.Qualifier()
}
}

return "sync"
},
"Exported": func(s string) string {
if s == "" {
return ""
}
for _, initialism := range golintInitialisms {
if strings.ToUpper(s) == initialism {
return initialism
}
}
return strings.ToUpper(s[0:1]) + s[1:]
},
}
125 changes: 125 additions & 0 deletions internal/template/template_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package template

import (
"fmt"
"strings"

"github.com/matryer/moq/internal/registry"
)

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

// MocksSomeMethod returns true of any one of the Mocks has at least 1
// method.
func (d Data) MocksSomeMethod() bool {
for _, m := range d.Mocks {
if len(m.Methods) > 0 {
return true
}
}

return false
}

// MockData is the data used to generate a mock for some interface.
type MockData struct {
InterfaceName string
MockName string
Methods []MethodData
}

// MethodData is the data which represents a method on some interface.
type MethodData struct {
Name string
Params []ParamData
Returns []ParamData
}

// ArgList is the string representation of method parameters, ex:
// 's string, n int, foo bar.Baz'.
func (m MethodData) ArgList() string {
params := make([]string, len(m.Params))
for i, p := range m.Params {
params[i] = p.MethodArg()
}
return strings.Join(params, ", ")
}

// ArgCallList is the string representation of method call parameters,
// ex: 's, n, foo'. In case of a last variadic parameter, it will be of
// the format 's, n, foos...'
func (m MethodData) ArgCallList() string {
params := make([]string, len(m.Params))
for i, p := range m.Params {
params[i] = p.CallName()
}
return strings.Join(params, ", ")
}

// ReturnArgTypeList is the string representation of method return
// types, ex: 'bar.Baz', '(string, error)'.
func (m MethodData) ReturnArgTypeList() string {
params := make([]string, len(m.Returns))
for i, p := range m.Returns {
params[i] = p.TypeString()
}
if len(m.Returns) > 1 {
return fmt.Sprintf("(%s)", strings.Join(params, ", "))
}
return strings.Join(params, ", ")
}

// ReturnArgNameList is the string representation of values being
// returned from the method, ex: 'foo', 's, err'.
func (m MethodData) ReturnArgNameList() string {
params := make([]string, len(m.Returns))
for i, p := range m.Returns {
params[i] = p.Name()
}
return strings.Join(params, ", ")
}

// ParamData is the data which represents a parameter to some method of
// an interface.
type ParamData struct {
Var *registry.Var
Variadic bool
}

// Name returns the name of the parameter.
func (p ParamData) Name() string {
return p.Var.Name
}

// MethodArg is the representation of the parameter in the function
// signature, ex: 'name a.Type'.
func (p ParamData) MethodArg() string {
if p.Variadic {
return fmt.Sprintf("%s ...%s", p.Name(), p.TypeString()[2:])
}
return fmt.Sprintf("%s %s", p.Name(), p.TypeString())
}

// CallName returns the string representation of the parameter to be
// used for a method call. For a variadic paramter, it will be of the
// format 'foos...'.
func (p ParamData) CallName() string {
if p.Variadic {
return p.Name() + "..."
}
return p.Name()
}

// TypeString returns the string representation of the type of the
// parameter.
func (p ParamData) TypeString() string {
return p.Var.TypeString()
}
55 changes: 55 additions & 0 deletions internal/template/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package template

import (
"go/types"
"testing"

"github.com/matryer/moq/internal/registry"
)

func TestTemplateFuncs(t *testing.T) {
t.Run("Exported", func(t *testing.T) {
f := templateFuncs["Exported"].(func(string) string)
if f("") != "" {
t.Errorf("Exported(...) want: ``; got: `%s`", f(""))
}
if f("var") != "Var" {
t.Errorf("Exported(...) want: `Var`; got: `%s`", f("var"))
}
})

t.Run("ImportStatement", func(t *testing.T) {
f := templateFuncs["ImportStatement"].(func(*registry.Package) string)
pkg := registry.NewPackage(types.NewPackage("xyz", "xyz"))
if f(pkg) != `"xyz"` {
t.Errorf("ImportStatement(...): want: `\"xyz\"`; got: `%s`", f(pkg))
}

pkg.Alias = "x"
if f(pkg) != `x "xyz"` {
t.Errorf("ImportStatement(...): want: `x \"xyz\"`; got: `%s`", f(pkg))
}
})

t.Run("SyncPkgQualifier", func(t *testing.T) {
f := templateFuncs["SyncPkgQualifier"].(func([]*registry.Package) string)
if f(nil) != "sync" {
t.Errorf("SyncPkgQualifier(...): want: `sync`; got: `%s`", f(nil))
}
imports := []*registry.Package{
registry.NewPackage(types.NewPackage("sync", "sync")),
registry.NewPackage(types.NewPackage("github.com/some/module", "module")),
}
if f(imports) != "sync" {
t.Errorf("SyncPkgQualifier(...): want: `sync`; got: `%s`", f(imports))
}

syncPkg := registry.NewPackage(types.NewPackage("sync", "sync"))
syncPkg.Alias = "stdsync"
otherSyncPkg := registry.NewPackage(types.NewPackage("github.com/someother/sync", "sync"))
imports = []*registry.Package{otherSyncPkg, syncPkg}
if f(imports) != "stdsync" {
t.Errorf("SyncPkgQualifier(...): want: `stdsync`; got: `%s`", f(imports))
}
})
}