diff --git a/README.md b/README.md index 4783569..c0c5ac1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # moq Interface mocking tool for go generate + +## Usage + +``` +moq Interface1 +``` diff --git a/example/example.go b/example/example.go new file mode 100644 index 0000000..5ec5384 --- /dev/null +++ b/example/example.go @@ -0,0 +1,15 @@ +package example + +import "context" + +type Person struct { + ID string + Name string + Company string + Website string +} + +type PersonStore interface { + Get(context.Context, string) (*Person, error) + Create(ctx context.Context, person *Person, confirm bool) error +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..b8a46dd --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "github.com/matryer/moq/package/moq" +) + +func main() { + + m := moq.New() + +} diff --git a/package/moq/moq.go b/package/moq/moq.go new file mode 100644 index 0000000..4c670a4 --- /dev/null +++ b/package/moq/moq.go @@ -0,0 +1,179 @@ +package moq + +import ( + "fmt" + "go/ast" + "go/importer" + "go/parser" + "go/token" + "go/types" + "html/template" + "io" + "os" + "strings" +) + +type Mocker struct { + src string + tmpl *template.Template + fset *token.FileSet + pkgs map[string]*ast.Package +} + +func New(src string) (*Mocker, error) { + fset := token.NewFileSet() + noTestFiles := func(i os.FileInfo) bool { + return !strings.HasSuffix(i.Name(), "_test.go") + } + pkgs, err := parser.ParseDir(fset, src, noTestFiles, parser.SpuriousErrors) + if err != nil { + return nil, err + } + tmpl, err := template.New("moq").Parse(moqTemplate) + if err != nil { + return nil, err + } + return &Mocker{ + src: src, + tmpl: tmpl, + fset: fset, + pkgs: pkgs, + }, nil +} + +// Mock generates a mock for the specified interface name. +func (m *Mocker) Mock(w io.Writer, name ...string) error { + var objs []*obj + for _, pkg := range m.pkgs { + i := 0 + files := make([]*ast.File, len(pkg.Files)) + for _, file := range pkg.Files { + files[i] = file + i++ + } + conf := types.Config{Importer: importer.Default()} + tpkg, err := conf.Check(m.src, m.fset, files, nil) + if err != nil { + return err + } + for _, n := range name { + iface := tpkg.Scope().Lookup(n) + if !types.IsInterface(iface.Type()) { + return fmt.Errorf("%s (%s) not an interface", n, iface.Type().String()) + } + iiface := iface.Type().Underlying().(*types.Interface).Complete() + obj := &obj{ + Name: n, + } + for i := 0; i < iiface.NumMethods(); i++ { + meth := iiface.Method(i) + sig := meth.Type().(*types.Signature) + method := &method{ + Name: meth.Name(), + } + obj.Methods = append(obj.Methods, method) + method.Params = extractArgs(sig.Params(), "in%d") + method.Returns = extractArgs(sig.Results(), "out%d") + } + objs = append(objs, obj) + + } + + } + err := m.tmpl.Execute(w, struct{ Objs []*obj }{Objs: objs}) + if err != nil { + return err + } + return nil +} + +func extractArgs(list *types.Tuple, nameFormat string) []*param { + var params []*param + for ii := 0; ii < list.Len(); ii++ { + p := list.At(ii) + name := p.Name() + if name == "" { + name = fmt.Sprintf(nameFormat, ii+1) + } + param := ¶m{ + Name: name, + Type: p.Type().String(), + } + params = append(params, param) + } + return params +} + +type obj struct { + Name string + Methods []*method +} +type method struct { + Name string + Params []*param + Returns []*param +} + +func (m *method) Arglist() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.String() + } + return strings.Join(params, ", ") +} + +func (m *method) ArgCallList() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.Name + } + return strings.Join(params, ", ") +} + +func (m *method) ReturnArglist() 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, ", ") +} + +type param struct { + Name string + Type string +} + +func (p param) String() string { + return fmt.Sprintf("%s %s", p.Name, p.TypeString()) +} + +func (p param) TypeString() string { + return p.Type +} + +var moqTemplate = ` +package todo + +// AUTOGENERATED BY MOQ +// github.com/matryer/moq +{{ range .Objs }} +// {{.Name}}Mock is a mock implementation of {{.Name}}. +type {{.Name}}Mock struct { +{{ range .Methods }} + // {{.Name}}Func mocks the {{.Name}} function. + {{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}} +{{ end }}} +{{ range .Methods }} +// {{.Name}} calls the mocked {{.Name}}Func function. +func (mock *{{.Name}}Mock) {{.Name}}({{ .Arglist }}) {{.ReturnArglist}} { + if mock.{{.Name}} == nil { + panic("moq: {{.Name}}Func is nil") + } + return mock.{{.Name}}Func({{ .ArgCallList }}) +} +{{ end }} +{{ end }} +` diff --git a/package/moq/moq_test.go b/package/moq/moq_test.go new file mode 100644 index 0000000..6d30489 --- /dev/null +++ b/package/moq/moq_test.go @@ -0,0 +1,24 @@ +package moq_test + +import ( + "bytes" + "log" + "testing" + + "github.com/matryer/moq/package/moq" +) + +func TestMoq(t *testing.T) { + + m, err := moq.New("../../example") + if err != nil { + t.Errorf("moq.New: %s", err) + } + var buf bytes.Buffer + err = m.Mock(&buf, "PersonStore") + if err != nil { + t.Errorf("m.Mock: %s", err) + } + log.Println(buf.String()) + +}