From 3fdb7f2a79bcf3195c036498cd815d8a4925d751 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 13:36:14 -0300 Subject: [PATCH 01/12] add replace-type parameter --- cmd/mockery.go | 1 + pkg/config/config.go | 3 +- pkg/fixtures/example_project/baz/foo.go | 12 +++ .../example_project/baz/internal/foo/foo.go | 6 ++ pkg/generator.go | 55 +++++++++++- pkg/generator_test.go | 84 +++++++++++++++++++ 6 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 pkg/fixtures/example_project/baz/foo.go create mode 100644 pkg/fixtures/example_project/baz/internal/foo/foo.go diff --git a/cmd/mockery.go b/cmd/mockery.go index 76e0d77f..dbc0c618 100644 --- a/cmd/mockery.go +++ b/cmd/mockery.go @@ -75,6 +75,7 @@ func NewRootCmd() *cobra.Command { pFlags.Bool("unroll-variadic", true, "For functions with variadic arguments, do not unroll the arguments into the underlying testify call. Instead, pass variadic slice as-is.") pFlags.Bool("exported", false, "Generates public mocks for private interfaces.") pFlags.Bool("with-expecter", false, "Generate expecter utility around mock's On, Run and Return methods with explicit types. This option is NOT compatible with -unroll-variadic=false") + pFlags.StringArray("replace-type", nil, "Replace types") viper.BindPFlags(pFlags) diff --git a/pkg/config/config.go b/pkg/config/config.go index b0ca2115..db92bf26 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -51,5 +51,6 @@ type Config struct { TestOnly bool UnrollVariadic bool `mapstructure:"unroll-variadic"` Version bool - WithExpecter bool `mapstructure:"with-expecter"` + WithExpecter bool `mapstructure:"with-expecter"` + ReplaceType []string `mapstructure:"replace-type"` } diff --git a/pkg/fixtures/example_project/baz/foo.go b/pkg/fixtures/example_project/baz/foo.go new file mode 100644 index 00000000..608b8f06 --- /dev/null +++ b/pkg/fixtures/example_project/baz/foo.go @@ -0,0 +1,12 @@ +package baz + +import ( + ifoo "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo" +) + +type Baz = ifoo.InternalBaz + +type Foo interface { + DoFoo() string + GetBaz() (*Baz, error) +} diff --git a/pkg/fixtures/example_project/baz/internal/foo/foo.go b/pkg/fixtures/example_project/baz/internal/foo/foo.go new file mode 100644 index 00000000..cb6930fe --- /dev/null +++ b/pkg/fixtures/example_project/baz/internal/foo/foo.go @@ -0,0 +1,6 @@ +package foo + +type InternalBaz struct { + One string + Two int +} diff --git a/pkg/generator.go b/pkg/generator.go index 69fc94b2..d0a84772 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -101,14 +101,67 @@ func (g *Generator) getPackageScopedType(ctx context.Context, o *types.TypeName) if o.Pkg() == nil || o.Pkg().Name() == "main" || (!g.KeepTree && g.InPackage && o.Pkg() == g.iface.Pkg) { return o.Name() } - return g.addPackageImport(ctx, o.Pkg()) + "." + o.Name() + pkg := g.addPackageImport(ctx, o.Pkg()) + name := o.Name() + g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + if o.Pkg().Path() == from.pkg && name == from.typ { + name = to.typ + return false + } + return true + }) + return pkg + "." + name } func (g *Generator) addPackageImport(ctx context.Context, pkg *types.Package) string { return g.addPackageImportWithName(ctx, pkg.Path(), pkg.Name()) } +type replaceType struct { + alias string + pkg string + typ string +} + +func parseReplaceType(t string) replaceType { + ret := replaceType{} + r := strings.SplitN(t, ":", 2) + if len(r) > 1 { + ret.alias = r[0] + t = r[1] + } + lastInd := strings.LastIndex(t, ".") + ret.pkg = t[:lastInd] + ret.typ = t[lastInd+1:] + return ret +} + +func (g *Generator) checkReplaceType(ctx context.Context, f func(from replaceType, to replaceType) bool) { + for _, replace := range g.ReplaceType { + r := strings.SplitN(replace, "=", 2) + if len(r) == 2 { + if !f(parseReplaceType(r[0]), parseReplaceType(r[1])) { + break + } + } else { + log := zerolog.Ctx(ctx) + log.Error().Msgf("invalid replace type value: %s", replace) + } + } +} + func (g *Generator) addPackageImportWithName(ctx context.Context, path, name string) string { + g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + if path == from.pkg { + path = to.pkg + if to.alias != "" { + name = to.alias + } + return false + } + return true + }) + if existingName, pathExists := g.packagePathToName[path]; pathExists { return existingName } diff --git a/pkg/generator_test.go b/pkg/generator_test.go index efe92eac..6963338e 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2329,6 +2329,90 @@ import mock "github.com/stretchr/testify/mock" s.checkPrologueGeneration(generator, expected) } +func (s *GeneratorSuite) TestInternalPackagePrologue() { + expected := `package mocks + +import baz "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz" +import mock "github.com/stretchr/testify/mock" + +` + generator := NewGenerator( + s.ctx, + config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + }}, + s.getInterfaceFromFile("example_project/baz/foo.go", "Foo"), + pkg, + ) + + s.checkPrologueGeneration(generator, expected) +} + +func (s *GeneratorSuite) TestInternalPackage() { + expected := `// Foo is an autogenerated mock type for the Foo type +type Foo struct { + mock.Mock +} + +// DoFoo provides a mock function with given fields: +func (_m *Foo) DoFoo() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetBaz provides a mock function with given fields: +func (_m *Foo) GetBaz() (*baz.Baz, error) { + ret := _m.Called() + + var r0 *baz.Baz + if rf, ok := ret.Get(0).(func() *baz.Baz); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*baz.Baz) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewFoo interface { + mock.TestingT + Cleanup(func()) +} + +// NewFoo creates a new instance of Foo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewFoo(t mockConstructorTestingTNewFoo) *Foo { + mock := &Foo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} +` + cfg := config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + }} + + s.checkGenerationWithConfig("example_project/baz/foo.go", "Foo", cfg, expected) +} + func (s *GeneratorSuite) TestGenericGenerator() { expected := `// RequesterGenerics is an autogenerated mock type for the RequesterGenerics type type RequesterGenerics[TAny interface{}, TComparable comparable, TSigned constraints.Signed, TIntf test.GetInt, TExternalIntf io.Writer, TGenIntf test.GetGeneric[TSigned], TInlineType interface{ ~int | ~uint }, TInlineTypeGeneric interface { From 36a8f1105e3c403dbe64fa1297b86263d8a6c67f Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 13:37:12 -0300 Subject: [PATCH 02/12] reorganization --- pkg/generator.go | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/pkg/generator.go b/pkg/generator.go index d0a84772..1a51ff0f 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -117,25 +117,6 @@ func (g *Generator) addPackageImport(ctx context.Context, pkg *types.Package) st return g.addPackageImportWithName(ctx, pkg.Path(), pkg.Name()) } -type replaceType struct { - alias string - pkg string - typ string -} - -func parseReplaceType(t string) replaceType { - ret := replaceType{} - r := strings.SplitN(t, ":", 2) - if len(r) > 1 { - ret.alias = r[0] - t = r[1] - } - lastInd := strings.LastIndex(t, ".") - ret.pkg = t[:lastInd] - ret.typ = t[lastInd+1:] - return ret -} - func (g *Generator) checkReplaceType(ctx context.Context, f func(from replaceType, to replaceType) bool) { for _, replace := range g.ReplaceType { r := strings.SplitN(replace, "=", 2) @@ -935,3 +916,22 @@ func resolveCollision(names map[string]struct{}, variable string) string { return ret } + +type replaceType struct { + alias string + pkg string + typ string +} + +func parseReplaceType(t string) replaceType { + ret := replaceType{} + r := strings.SplitN(t, ":", 2) + if len(r) > 1 { + ret.alias = r[0] + t = r[1] + } + lastInd := strings.LastIndex(t, ".") + ret.pkg = t[:lastInd] + ret.typ = t[lastInd+1:] + return ret +} From 604a58128f530c23b701e6bfbec7f386e36bb706 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 13:45:34 -0300 Subject: [PATCH 03/12] flags test --- cmd/mockery_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/mockery_test.go b/cmd/mockery_test.go index 6745ef26..7f6c03e0 100644 --- a/cmd/mockery_test.go +++ b/cmd/mockery_test.go @@ -47,6 +47,7 @@ func TestConfigEnvFlags(t *testing.T) { UnrollVariadic: false, Exported: true, WithExpecter: true, + ReplaceType: []string{}, } env(t, "CONFIG", expected.Config) @@ -77,6 +78,7 @@ func TestConfigEnvFlags(t *testing.T) { env(t, "UNROLL_VARIADIC", fmt.Sprint(expected.UnrollVariadic)) env(t, "EXPORTED", fmt.Sprint(expected.Exported)) env(t, "WITH_EXPECTER", fmt.Sprint(expected.WithExpecter)) + env(t, "REPLACE_TYPE", strings.Join(expected.ReplaceType, ",")) initConfig(nil, nil) From 3862af797ecf32bbbfa59fde0f2d5ae8929c62de Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 14:20:58 -0300 Subject: [PATCH 04/12] fix parse replace type --- pkg/generator.go | 11 ++++++++--- pkg/generator_test.go | 25 +++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/pkg/generator.go b/pkg/generator.go index 1a51ff0f..55260d6b 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -930,8 +930,13 @@ func parseReplaceType(t string) replaceType { ret.alias = r[0] t = r[1] } - lastInd := strings.LastIndex(t, ".") - ret.pkg = t[:lastInd] - ret.typ = t[lastInd+1:] + lastDot := strings.LastIndex(t, ".") + lastSlash := strings.LastIndex(t, "/") + if lastDot == -1 || (lastSlash > -1 && lastDot < lastSlash) { + ret.pkg = t + } else { + ret.pkg = t[:lastDot] + ret.typ = t[lastDot+1:] + } return ret } diff --git a/pkg/generator_test.go b/pkg/generator_test.go index 6963338e..3773cf91 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2775,3 +2775,28 @@ func TestGeneratorSuite(t *testing.T) { generatorSuite := new(GeneratorSuite) suite.Run(t, generatorSuite) } + +func TestParseReplaceType(t *testing.T) { + tests := []struct { + value string + expected replaceType + }{ + { + value: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz", + expected: replaceType{alias: "", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo", typ: "InternalBaz"}, + }, + { + value: "baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + expected: replaceType{alias: "baz", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", typ: "Baz"}, + }, + { + value: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", + expected: replaceType{alias: "", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", typ: ""}, + }, + } + + for _, test := range tests { + actual := parseReplaceType(test.value) + assert.Equal(t, test.expected, actual) + } +} From 66a37ac953aea82fa76dd2eca64c42e10370c586 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 14:42:48 -0300 Subject: [PATCH 05/12] README --- README.md | 108 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 95 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ab33ea5c..db0a6e36 100644 --- a/README.md +++ b/README.md @@ -391,24 +391,106 @@ The constructor sets up common functionalities automatically - The `AssertExpectations` method is registered to be called at the end of the tests via `t.Cleanup()` method. - The testing.TB interface is registered on the `mock.Mock` so that tests don't panic when a call on the mock is unexpected. +Replace Types +------------- + +The `replace-type` parameter allows adding a list of type replacements to be made in package and/or type names. +This can help overcome some parsing problems like type aliases that the Go parser doesn't provide enough information. + +```shell +mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/baz.Baz +``` + +This parameter can be specified multiple times. + +This will replace any imported named `"github.com/vektra/mockery/v2/baz/internal/foo"` +with `baz "github.com/vektra/mockery/v2/baz"`. The alias is defined with `:` before +the package name. Also, the `InternalBaz` type that comes from this package will be renamed to `BazĀ“. + +This next example fixes a common problem of type aliases that point to an internal package. + +`cloud.google.com/go/pubsub.Message` is a type alias defined like this: + +```go +import ( + ipubsub "cloud.google.com/go/internal/pubsub" +) + +type Message = ipubsub.Message +``` + +The Go parser that mockery uses doesn't provide a way to detect this alias and sends the application the package and +type name of the type in the internal package, which will not work. + +We can use "replace-type" with only the package part to replace any import of `cloud.google.com/go/internal/pubsub` to +`cloud.google.com/go/pubsub`. We don't need to change the alias or type name in this case, because they are `pubsub` +and `Message` in both cases. + +```shell +mockery --replace-type cloud.google.com/go/internal/pubsub=cloud.google.com/go/pubsub +``` + +Original source: + +```go +import ( + "cloud.google.com/go/pubsub" +) + +type Handler struct { + HandleMessage(m pubsub.Message) error +} +``` + +Mock generated without this parameter: + +```go +import ( + mock "github.com/stretchr/testify/mock" + + pubsub "cloud.google.com/go/internal/pubsub" +) + +func (_m *Handler) HandleMessage(m pubsub.Message) error { + // ... + return nil +} +``` + +Mock generated with this parameter. + +```go +import ( + mock "github.com/stretchr/testify/mock" + + pubsub "cloud.google.com/go/pubsub" +) + +func (_m *Handler) HandleMessage(m pubsub.Message) error { + // ... + return nil +} +``` + Extended Flag Descriptions -------------------------- The following descriptions provide additional elaboration on a few common parameters. -| flag name | description | -|---|---| -| `--name` | The `--name` option takes either the name or matching regular expression of the interface to generate mock(s) for. | -| `--all` | It's common for a big package to have a lot of interfaces, so mockery provides `--all`. This option will tell mockery to scan all files under the directory named by `--dir` ("." by default) and generates mocks for any interfaces it finds. This option implies `--recursive=true`. | -| `--recursive` | Use the `--recursive` option to search subdirectories for the interface(s). This option is only compatible with `--name`. The `--all` option implies `--recursive=true`. | -| `--output` | mockery always generates files with the package `mocks` to keep things clean and simple. You can control which mocks directory is used by using `--output`, which defaults to `./mocks`. | -|`--outpkg`| Use `--outpkg` to specify the package name of the generated mocks.| -| `--inpackage` and `--keeptree` | For some complex repositories, there could be multiple interfaces with the same name but in different packages. In that case, `--inpackage` allows generating the mocked interfaces directly in the package that it mocks. In the case you don't want to generate the mocks into the package but want to keep a similar structure, use the option `--keeptree`. | -| `--filename` | Use the `--filename` and `--structname` to override the default generated file and struct name. These options are only compatible with non-regular expressions in `--name`, where only one mock is generated. | -| `--case` | mockery generates files using the casing of the original interface name. This can be modified by specifying `--case underscore` to format the generated file name using underscore casing. | -| `--print` | Use `mockery --print` to have the resulting code printed out instead of written to disk. | -| `--exported` | Use `mockery --exported` to generate public mocks for private interfaces. | -| `--with-expecter` | Use `mockery --with-expecter` to generate `EXPECT()` methods for your mocks. This is the preferred way to setup your mocks. | +| flag name | description | +|-------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `--name` | The `--name` option takes either the name or matching regular expression of the interface to generate mock(s) for. | +| `--all` | It's common for a big package to have a lot of interfaces, so mockery provides `--all`. This option will tell mockery to scan all files under the directory named by `--dir` ("." by default) and generates mocks for any interfaces it finds. This option implies `--recursive=true`. | +| `--recursive` | Use the `--recursive` option to search subdirectories for the interface(s). This option is only compatible with `--name`. The `--all` option implies `--recursive=true`. | +| `--output` | mockery always generates files with the package `mocks` to keep things clean and simple. You can control which mocks directory is used by using `--output`, which defaults to `./mocks`. | +| `--outpkg` | Use `--outpkg` to specify the package name of the generated mocks. | +| `--inpackage` and `--keeptree` | For some complex repositories, there could be multiple interfaces with the same name but in different packages. In that case, `--inpackage` allows generating the mocked interfaces directly in the package that it mocks. In the case you don't want to generate the mocks into the package but want to keep a similar structure, use the option `--keeptree`. | +| `--filename` | Use the `--filename` and `--structname` to override the default generated file and struct name. These options are only compatible with non-regular expressions in `--name`, where only one mock is generated. | +| `--case` | mockery generates files using the casing of the original interface name. This can be modified by specifying `--case underscore` to format the generated file name using underscore casing. | +| `--print` | Use `mockery --print` to have the resulting code printed out instead of written to disk. | +| `--exported` | Use `mockery --exported` to generate public mocks for private interfaces. | +| `--with-expecter` | Use `mockery --with-expecter` to generate `EXPECT()` methods for your mocks. This is the preferred way to setup your mocks. | +| `--replace-type source=destination` | Replaces aliases, packages and/or types during generation. | Mocking interfaces in `main` ---------------------------- From 9249785e0ee4cb21c7483f3277c6c2312231d34b Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 14:44:50 -0300 Subject: [PATCH 06/12] README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index db0a6e36..f0eee452 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Table of Contents + [Notes](#notes) - [Expecter Interfaces](#expecter-interfaces) - [Mock constructors](#mock-constructors) +- [Replace Types](#replace-types) - [Extended Flag Descriptions](#extended-flag-descriptions) - [Mocking interfaces in `main`](#mocking-interfaces-in--main-) - [Semantic Versioning](#semantic-versioning) From c152fc09e07de32ceb5c1fdca555dd6b7daa7be3 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 14:47:25 -0300 Subject: [PATCH 07/12] README --- README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index f0eee452..4b9548fe 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ This parameter can be specified multiple times. This will replace any imported named `"github.com/vektra/mockery/v2/baz/internal/foo"` with `baz "github.com/vektra/mockery/v2/baz"`. The alias is defined with `:` before -the package name. Also, the `InternalBaz` type that comes from this package will be renamed to `BazĀ“. +the package name. Also, the `InternalBaz` type that comes from this package will be renamed to `baz.Baz`. This next example fixes a common problem of type aliases that point to an internal package. @@ -414,7 +414,7 @@ This next example fixes a common problem of type aliases that point to an intern ```go import ( - ipubsub "cloud.google.com/go/internal/pubsub" + ipubsub "cloud.google.com/go/internal/pubsub" ) type Message = ipubsub.Message @@ -435,7 +435,7 @@ Original source: ```go import ( - "cloud.google.com/go/pubsub" + "cloud.google.com/go/pubsub" ) type Handler struct { @@ -447,14 +447,14 @@ Mock generated without this parameter: ```go import ( - mock "github.com/stretchr/testify/mock" + mock "github.com/stretchr/testify/mock" - pubsub "cloud.google.com/go/internal/pubsub" + pubsub "cloud.google.com/go/internal/pubsub" ) func (_m *Handler) HandleMessage(m pubsub.Message) error { - // ... - return nil + // ... + return nil } ``` @@ -462,14 +462,14 @@ Mock generated with this parameter. ```go import ( - mock "github.com/stretchr/testify/mock" + mock "github.com/stretchr/testify/mock" - pubsub "cloud.google.com/go/pubsub" + pubsub "cloud.google.com/go/pubsub" ) func (_m *Handler) HandleMessage(m pubsub.Message) error { - // ... - return nil + // ... + return nil } ``` From 17b21368024a546197181d556abe8959f87f891d Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Wed, 8 Feb 2023 13:36:14 -0300 Subject: [PATCH 08/12] add replace-type parameter --- cmd/mockery.go | 1 + cmd/mockery_test.go | 2 + docs/configuration.md | 3 +- docs/features.md | 81 +++++++++++++ pkg/config/config.go | 3 +- pkg/fixtures/example_project/baz/foo.go | 12 ++ .../example_project/baz/internal/foo/foo.go | 6 + pkg/generator.go | 60 +++++++++- pkg/generator_test.go | 109 ++++++++++++++++++ 9 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 pkg/fixtures/example_project/baz/foo.go create mode 100644 pkg/fixtures/example_project/baz/internal/foo/foo.go diff --git a/cmd/mockery.go b/cmd/mockery.go index ae502ac1..8be222e2 100644 --- a/cmd/mockery.go +++ b/cmd/mockery.go @@ -76,6 +76,7 @@ func NewRootCmd() *cobra.Command { pFlags.Bool("unroll-variadic", true, "For functions with variadic arguments, do not unroll the arguments into the underlying testify call. Instead, pass variadic slice as-is.") pFlags.Bool("exported", false, "Generates public mocks for private interfaces.") pFlags.Bool("with-expecter", false, "Generate expecter utility around mock's On, Run and Return methods with explicit types. This option is NOT compatible with -unroll-variadic=false") + pFlags.StringArray("replace-type", nil, "Replace types") viper.BindPFlags(pFlags) diff --git a/cmd/mockery_test.go b/cmd/mockery_test.go index 6745ef26..7f6c03e0 100644 --- a/cmd/mockery_test.go +++ b/cmd/mockery_test.go @@ -47,6 +47,7 @@ func TestConfigEnvFlags(t *testing.T) { UnrollVariadic: false, Exported: true, WithExpecter: true, + ReplaceType: []string{}, } env(t, "CONFIG", expected.Config) @@ -77,6 +78,7 @@ func TestConfigEnvFlags(t *testing.T) { env(t, "UNROLL_VARIADIC", fmt.Sprint(expected.UnrollVariadic)) env(t, "EXPORTED", fmt.Sprint(expected.Exported)) env(t, "WITH_EXPECTER", fmt.Sprint(expected.WithExpecter)) + env(t, "REPLACE_TYPE", strings.Join(expected.ReplaceType, ",")) initConfig(nil, nil) diff --git a/docs/configuration.md b/docs/configuration.md index 35627d33..56d7fdd9 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -46,4 +46,5 @@ Parameter Descriptions | `exported` | Use `exported: True` to generate public mocks for private interfaces. | | `with-expecter` | Use `with-expecter: True` to generate `EXPECT()` methods for your mocks. This is the preferred way to setup your mocks. | | `testonly` | Prepend every mock file with `_test.go`. This is useful in cases where you are generating mocks `inpackage` but don't want the mocks to be visible to code outside of tests. | -| `inpackage-suffix` | When `inpackage-suffix` is set to `True`, mock files are suffixed with `_mock` instead of being prefixed with `mock_` for InPackage mocks | \ No newline at end of file +| `inpackage-suffix` | When `inpackage-suffix` is set to `True`, mock files are suffixed with `_mock` instead of being prefixed with `mock_` for InPackage mocks | +| `replace-type source=destination` | Replaces aliases, packages and/or types during generation.| \ No newline at end of file diff --git a/docs/features.md b/docs/features.md index 4fd504a1..8ed66e0e 100644 --- a/docs/features.md +++ b/docs/features.md @@ -68,3 +68,84 @@ Return( }, ) ``` + +Replace Types +------------- + +The `replace-type` parameter allows adding a list of type replacements to be made in package and/or type names. +This can help overcome some parsing problems like type aliases that the Go parser doesn't provide enough information. + +```shell +mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/baz.Baz +``` + +This parameter can be specified multiple times. + +This will replace any imported named `"github.com/vektra/mockery/v2/baz/internal/foo"` +with `baz "github.com/vektra/mockery/v2/baz"`. The alias is defined with `:` before +the package name. Also, the `InternalBaz` type that comes from this package will be renamed to `baz.Baz`. + +This next example fixes a common problem of type aliases that point to an internal package. + +`cloud.google.com/go/pubsub.Message` is a type alias defined like this: + +```go +import ( + ipubsub "cloud.google.com/go/internal/pubsub" +) + +type Message = ipubsub.Message +``` + +The Go parser that mockery uses doesn't provide a way to detect this alias and sends the application the package and +type name of the type in the internal package, which will not work. + +We can use "replace-type" with only the package part to replace any import of `cloud.google.com/go/internal/pubsub` to +`cloud.google.com/go/pubsub`. We don't need to change the alias or type name in this case, because they are `pubsub` +and `Message` in both cases. + +```shell +mockery --replace-type cloud.google.com/go/internal/pubsub=cloud.google.com/go/pubsub +``` + +Original source: + +```go +import ( + "cloud.google.com/go/pubsub" +) + +type Handler struct { + HandleMessage(m pubsub.Message) error +} +``` + +Mock generated without this parameter: + +```go +import ( + mock "github.com/stretchr/testify/mock" + + pubsub "cloud.google.com/go/internal/pubsub" +) + +func (_m *Handler) HandleMessage(m pubsub.Message) error { + // ... + return nil +} +``` + +Mock generated with this parameter. + +```go +import ( + mock "github.com/stretchr/testify/mock" + + pubsub "cloud.google.com/go/pubsub" +) + +func (_m *Handler) HandleMessage(m pubsub.Message) error { + // ... + return nil +} +``` diff --git a/pkg/config/config.go b/pkg/config/config.go index c4ef4671..5b75e0e7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -52,5 +52,6 @@ type Config struct { TestOnly bool UnrollVariadic bool `mapstructure:"unroll-variadic"` Version bool - WithExpecter bool `mapstructure:"with-expecter"` + WithExpecter bool `mapstructure:"with-expecter"` + ReplaceType []string `mapstructure:"replace-type"` } diff --git a/pkg/fixtures/example_project/baz/foo.go b/pkg/fixtures/example_project/baz/foo.go new file mode 100644 index 00000000..608b8f06 --- /dev/null +++ b/pkg/fixtures/example_project/baz/foo.go @@ -0,0 +1,12 @@ +package baz + +import ( + ifoo "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo" +) + +type Baz = ifoo.InternalBaz + +type Foo interface { + DoFoo() string + GetBaz() (*Baz, error) +} diff --git a/pkg/fixtures/example_project/baz/internal/foo/foo.go b/pkg/fixtures/example_project/baz/internal/foo/foo.go new file mode 100644 index 00000000..cb6930fe --- /dev/null +++ b/pkg/fixtures/example_project/baz/internal/foo/foo.go @@ -0,0 +1,6 @@ +package foo + +type InternalBaz struct { + One string + Two int +} diff --git a/pkg/generator.go b/pkg/generator.go index 7159dced..85569982 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -101,14 +101,48 @@ func (g *Generator) getPackageScopedType(ctx context.Context, o *types.TypeName) if o.Pkg() == nil || o.Pkg().Name() == "main" || (!g.KeepTree && g.InPackage && o.Pkg() == g.iface.Pkg) { return o.Name() } - return g.addPackageImport(ctx, o.Pkg()) + "." + o.Name() + pkg := g.addPackageImport(ctx, o.Pkg()) + name := o.Name() + g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + if o.Pkg().Path() == from.pkg && name == from.typ { + name = to.typ + return false + } + return true + }) + return pkg + "." + name } func (g *Generator) addPackageImport(ctx context.Context, pkg *types.Package) string { return g.addPackageImportWithName(ctx, pkg.Path(), pkg.Name()) } +func (g *Generator) checkReplaceType(ctx context.Context, f func(from replaceType, to replaceType) bool) { + for _, replace := range g.ReplaceType { + r := strings.SplitN(replace, "=", 2) + if len(r) == 2 { + if !f(parseReplaceType(r[0]), parseReplaceType(r[1])) { + break + } + } else { + log := zerolog.Ctx(ctx) + log.Error().Msgf("invalid replace type value: %s", replace) + } + } +} + func (g *Generator) addPackageImportWithName(ctx context.Context, path, name string) string { + g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + if path == from.pkg { + path = to.pkg + if to.alias != "" { + name = to.alias + } + return false + } + return true + }) + if existingName, pathExists := g.packagePathToName[path]; pathExists { return existingName } @@ -908,3 +942,27 @@ func resolveCollision(names []string, variable string) string { return ret } + +type replaceType struct { + alias string + pkg string + typ string +} + +func parseReplaceType(t string) replaceType { + ret := replaceType{} + r := strings.SplitN(t, ":", 2) + if len(r) > 1 { + ret.alias = r[0] + t = r[1] + } + lastDot := strings.LastIndex(t, ".") + lastSlash := strings.LastIndex(t, "/") + if lastDot == -1 || (lastSlash > -1 && lastDot < lastSlash) { + ret.pkg = t + } else { + ret.pkg = t[:lastDot] + ret.typ = t[lastDot+1:] + } + return ret +} diff --git a/pkg/generator_test.go b/pkg/generator_test.go index aa940faf..4053ba8f 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2411,6 +2411,90 @@ import mock "github.com/stretchr/testify/mock" s.checkPrologueGeneration(generator, expected) } +func (s *GeneratorSuite) TestInternalPackagePrologue() { + expected := `package mocks + +import baz "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz" +import mock "github.com/stretchr/testify/mock" + +` + generator := NewGenerator( + s.ctx, + config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + }}, + s.getInterfaceFromFile("example_project/baz/foo.go", "Foo"), + pkg, + ) + + s.checkPrologueGeneration(generator, expected) +} + +func (s *GeneratorSuite) TestInternalPackage() { + expected := `// Foo is an autogenerated mock type for the Foo type +type Foo struct { + mock.Mock +} + +// DoFoo provides a mock function with given fields: +func (_m *Foo) DoFoo() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// GetBaz provides a mock function with given fields: +func (_m *Foo) GetBaz() (*baz.Baz, error) { + ret := _m.Called() + + var r0 *baz.Baz + if rf, ok := ret.Get(0).(func() *baz.Baz); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*baz.Baz) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +type mockConstructorTestingTNewFoo interface { + mock.TestingT + Cleanup(func()) +} + +// NewFoo creates a new instance of Foo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +func NewFoo(t mockConstructorTestingTNewFoo) *Foo { + mock := &Foo{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} +` + cfg := config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + }} + + s.checkGenerationWithConfig("example_project/baz/foo.go", "Foo", cfg, expected) +} + func (s *GeneratorSuite) TestGenericGenerator() { expected := `// RequesterGenerics is an autogenerated mock type for the RequesterGenerics type type RequesterGenerics[TAny interface{}, TComparable comparable, TSigned constraints.Signed, TIntf test.GetInt, TExternalIntf io.Writer, TGenIntf test.GetGeneric[TSigned], TInlineType interface{ ~int | ~uint }, TInlineTypeGeneric interface { @@ -2799,3 +2883,28 @@ func TestGeneratorSuite(t *testing.T) { generatorSuite := new(GeneratorSuite) suite.Run(t, generatorSuite) } + +func TestParseReplaceType(t *testing.T) { + tests := []struct { + value string + expected replaceType + }{ + { + value: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz", + expected: replaceType{alias: "", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo", typ: "InternalBaz"}, + }, + { + value: "baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", + expected: replaceType{alias: "baz", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", typ: "Baz"}, + }, + { + value: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", + expected: replaceType{alias: "", pkg: "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz", typ: ""}, + }, + } + + for _, test := range tests { + actual := parseReplaceType(test.value) + assert.Equal(t, test.expected, actual) + } +} From 8ca9a1d9fb09e8fae00044b1a3603b65ce492704 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Tue, 21 Feb 2023 10:26:02 -0300 Subject: [PATCH 09/12] cache replace types --- docs/features.md | 8 ++++---- pkg/generator.go | 45 +++++++++++++++++++++++++++++-------------- pkg/generator_test.go | 7 +++++-- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/docs/features.md b/docs/features.md index e4f3ff5d..ce9f8e89 100644 --- a/docs/features.md +++ b/docs/features.md @@ -105,12 +105,12 @@ Replace Types The `replace-type` parameter allows adding a list of type replacements to be made in package and/or type names. This can help overcome some parsing problems like type aliases that the Go parser doesn't provide enough information. +This parameter can be specified multiple times. + ```shell mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/baz.Baz ``` -This parameter can be specified multiple times. - This will replace any imported named `"github.com/vektra/mockery/v2/baz/internal/foo"` with `baz "github.com/vektra/mockery/v2/baz"`. The alias is defined with `:` before the package name. Also, the `InternalBaz` type that comes from this package will be renamed to `baz.Baz`. @@ -150,7 +150,7 @@ type Handler struct { } ``` -Mock generated without this parameter: +Invalid mock generated without this parameter (points to an `internal` folder): ```go import ( @@ -165,7 +165,7 @@ func (_m *Handler) HandleMessage(m pubsub.Message) error { } ``` -Mock generated with this parameter. +Correct mock generated with this parameter. ```go import ( diff --git a/pkg/generator.go b/pkg/generator.go index 85569982..ceaa7c77 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -38,6 +38,7 @@ type Generator struct { localizationCache map[string]string packagePathToName map[string]string nameToPackagePath map[string]string + replaceTypeCache []*replaceTypeItem } // NewGenerator builds a Generator. @@ -51,6 +52,7 @@ func NewGenerator(ctx context.Context, c config.Config, iface *Interface, pkg st nameToPackagePath: make(map[string]string), } + g.parseReplaceTypes(ctx) g.addPackageImportWithName(ctx, "github.com/stretchr/testify/mock", "mock") return g @@ -103,7 +105,7 @@ func (g *Generator) getPackageScopedType(ctx context.Context, o *types.TypeName) } pkg := g.addPackageImport(ctx, o.Pkg()) name := o.Name() - g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + g.checkReplaceType(ctx, func(from *replaceType, to *replaceType) bool { if o.Pkg().Path() == from.pkg && name == from.typ { name = to.typ return false @@ -117,22 +119,16 @@ func (g *Generator) addPackageImport(ctx context.Context, pkg *types.Package) st return g.addPackageImportWithName(ctx, pkg.Path(), pkg.Name()) } -func (g *Generator) checkReplaceType(ctx context.Context, f func(from replaceType, to replaceType) bool) { - for _, replace := range g.ReplaceType { - r := strings.SplitN(replace, "=", 2) - if len(r) == 2 { - if !f(parseReplaceType(r[0]), parseReplaceType(r[1])) { - break - } - } else { - log := zerolog.Ctx(ctx) - log.Error().Msgf("invalid replace type value: %s", replace) +func (g *Generator) checkReplaceType(ctx context.Context, f func(from *replaceType, to *replaceType) bool) { + for _, item := range g.replaceTypeCache { + if !f(item.from, item.to) { + break } } } func (g *Generator) addPackageImportWithName(ctx context.Context, path, name string) string { - g.checkReplaceType(ctx, func(from replaceType, to replaceType) bool { + g.checkReplaceType(ctx, func(from *replaceType, to *replaceType) bool { if path == from.pkg { path = to.pkg if to.alias != "" { @@ -153,6 +149,22 @@ func (g *Generator) addPackageImportWithName(ctx context.Context, path, name str return nonConflictingName } +func (g *Generator) parseReplaceTypes(ctx context.Context) { + for _, replace := range g.Config.ReplaceType { + r := strings.SplitN(replace, "=", 2) + if len(r) != 2 { + log := zerolog.Ctx(ctx) + log.Error().Msgf("invalid replace type value: %s", replace) + continue + } + + g.replaceTypeCache = append(g.replaceTypeCache, &replaceTypeItem{ + from: parseReplaceType(r[0]), + to: parseReplaceType(r[1]), + }) + } +} + func (g *Generator) getNonConflictingName(path, name string) string { if !g.importNameExists(name) && (!g.InPackage || g.iface.Pkg.Name() != name) { // do not allow imports with the same name as the package when inPackage @@ -949,8 +961,13 @@ type replaceType struct { typ string } -func parseReplaceType(t string) replaceType { - ret := replaceType{} +type replaceTypeItem struct { + from *replaceType + to *replaceType +} + +func parseReplaceType(t string) *replaceType { + ret := &replaceType{} r := strings.SplitN(t, ":", 2) if len(r) > 1 { ret.alias = r[0] diff --git a/pkg/generator_test.go b/pkg/generator_test.go index 4053ba8f..f46257be 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2455,6 +2455,10 @@ func (_m *Foo) GetBaz() (*baz.Baz, error) { ret := _m.Called() var r0 *baz.Baz + var r1 error + if rf, ok := ret.Get(0).(func() (*baz.Baz, error)); ok { + return rf() + } if rf, ok := ret.Get(0).(func() *baz.Baz); ok { r0 = rf() } else { @@ -2463,7 +2467,6 @@ func (_m *Foo) GetBaz() (*baz.Baz, error) { } } - var r1 error if rf, ok := ret.Get(1).(func() error); ok { r1 = rf() } else { @@ -2905,6 +2908,6 @@ func TestParseReplaceType(t *testing.T) { for _, test := range tests { actual := parseReplaceType(test.value) - assert.Equal(t, test.expected, actual) + assert.Equal(t, test.expected, *actual) } } From fefb94be697db037ca9f7fa9cb9729ff3624eba5 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Fri, 10 Mar 2023 08:44:43 -0300 Subject: [PATCH 10/12] merge master --- pkg/generator.go | 3 ++- pkg/generator_test.go | 4 ++-- pkg/outputter.go | 1 + pkg/walker.go | 2 ++ 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg/generator.go b/pkg/generator.go index 8d7afbf6..62b3a9e9 100644 --- a/pkg/generator.go +++ b/pkg/generator.go @@ -61,6 +61,7 @@ type GeneratorConfig struct { StructName string UnrollVariadic bool WithExpecter bool + ReplaceType []string } // Generator is responsible for generating the string containing @@ -206,7 +207,7 @@ func (g *Generator) addPackageImportWithName(ctx context.Context, path, name str } func (g *Generator) parseReplaceTypes(ctx context.Context) { - for _, replace := range g.Config.ReplaceType { + for _, replace := range g.config.ReplaceType { r := strings.SplitN(replace, "=", 2) if len(r) != 2 { log := zerolog.Ctx(ctx) diff --git a/pkg/generator_test.go b/pkg/generator_test.go index 26a1b162..0685be4c 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2433,7 +2433,7 @@ import mock "github.com/stretchr/testify/mock" ` generator := NewGenerator( s.ctx, - config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + GeneratorConfig{InPackage: false, ReplaceType: []string{ "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", }}, s.getInterfaceFromFile("example_project/baz/foo.go", "Foo"), @@ -2504,7 +2504,7 @@ func NewFoo(t mockConstructorTestingTNewFoo) *Foo { return mock } ` - cfg := config.Config{InPackage: false, LogLevel: "debug", ReplaceType: []string{ + cfg := GeneratorConfig{InPackage: false, ReplaceType: []string{ "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", }} diff --git a/pkg/outputter.go b/pkg/outputter.go index 34b43617..036ed034 100644 --- a/pkg/outputter.go +++ b/pkg/outputter.go @@ -240,6 +240,7 @@ func (m *Outputter) Generate(ctx context.Context, iface *Interface) error { StructName: interfaceConfig.StructName, UnrollVariadic: interfaceConfig.UnrollVariadic, WithExpecter: interfaceConfig.WithExpecter, + ReplaceType: interfaceConfig.ReplaceType, } generator := NewGenerator(ctx, g, iface, "") diff --git a/pkg/walker.go b/pkg/walker.go index 29cea8fd..b214339d 100644 --- a/pkg/walker.go +++ b/pkg/walker.go @@ -118,6 +118,7 @@ type GeneratorVisitorConfig struct { StructName string UnrollVariadic bool WithExpecter bool + ReplaceType []string } type GeneratorVisitor struct { @@ -173,6 +174,7 @@ func (v *GeneratorVisitor) VisitWalk(ctx context.Context, iface *Interface) erro StructName: v.config.StructName, UnrollVariadic: v.config.UnrollVariadic, WithExpecter: v.config.WithExpecter, + ReplaceType: v.config.ReplaceType, } gen := NewGenerator(ctx, generatorConfig, iface, "") From 6bf1f7a929162aeaac4a0cf6429cf75c5d8a3781 Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Fri, 10 Mar 2023 08:45:15 -0300 Subject: [PATCH 11/12] rename test --- pkg/generator_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/generator_test.go b/pkg/generator_test.go index 0685be4c..ea0b3456 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -2424,7 +2424,7 @@ import mock "github.com/stretchr/testify/mock" s.checkPrologueGeneration(generator, expected) } -func (s *GeneratorSuite) TestInternalPackagePrologue() { +func (s *GeneratorSuite) TestReplaceTypePackagePrologue() { expected := `package mocks import baz "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz" @@ -2443,7 +2443,7 @@ import mock "github.com/stretchr/testify/mock" s.checkPrologueGeneration(generator, expected) } -func (s *GeneratorSuite) TestInternalPackage() { +func (s *GeneratorSuite) TestReplaceTypePackage() { expected := `// Foo is an autogenerated mock type for the Foo type type Foo struct { mock.Mock From ec95f73729c594c211c3c1a6a09916e3d6b09e2f Mon Sep 17 00:00:00 2001 From: Rangel Reale Date: Fri, 10 Mar 2023 09:51:33 -0300 Subject: [PATCH 12/12] use regexp to test --- pkg/generator_test.go | 99 +++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 61 deletions(-) diff --git a/pkg/generator_test.go b/pkg/generator_test.go index ea0b3456..f2d1c927 100644 --- a/pkg/generator_test.go +++ b/pkg/generator_test.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "regexp" "strings" "testing" @@ -85,6 +86,37 @@ func (s *GeneratorSuite) checkGenerationWithConfig( return generator } +type regexpExpected struct { + shouldMatch bool + re *regexp.Regexp +} + +func (s *GeneratorSuite) checkGenerationRegexWithConfig( + filepath, interfaceName string, cfg GeneratorConfig, expected []regexpExpected, +) *Generator { + generator := s.getGeneratorWithConfig(filepath, interfaceName, cfg) + err := generator.Generate(s.ctx) + s.NoError(err, "The generator ran without errors.") + if err != nil { + return generator + } + // Mirror the formatting done by normally done by golang.org/x/tools/imports in Generator.Write. + // + // While we could possibly reuse Generator.Write here in addition to Generator.Generate, + // it would require changing Write's signature to accept custom options, specifically to + // allow the fragments in preexisting cases. It's assumed that this approximation, + // just formatting the source, is sufficient for the needs of the current test styles. + var actual []byte + actual, fmtErr := format.Source(generator.buf.Bytes()) + s.NoError(fmtErr, "The formatter ran without errors.") + + for _, re := range expected { + s.Equalf(re.shouldMatch, re.re.Match(actual), "match '%s' should be %t", re.re.String(), re.shouldMatch) + } + + return generator +} + func (s *GeneratorSuite) getGenerator( filepath, interfaceName string, inPackage bool, structName string, ) *Generator { @@ -2444,71 +2476,16 @@ import mock "github.com/stretchr/testify/mock" } func (s *GeneratorSuite) TestReplaceTypePackage() { - expected := `// Foo is an autogenerated mock type for the Foo type -type Foo struct { - mock.Mock -} - -// DoFoo provides a mock function with given fields: -func (_m *Foo) DoFoo() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// GetBaz provides a mock function with given fields: -func (_m *Foo) GetBaz() (*baz.Baz, error) { - ret := _m.Called() - - var r0 *baz.Baz - var r1 error - if rf, ok := ret.Get(0).(func() (*baz.Baz, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() *baz.Baz); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*baz.Baz) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -type mockConstructorTestingTNewFoo interface { - mock.TestingT - Cleanup(func()) -} - -// NewFoo creates a new instance of Foo. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -func NewFoo(t mockConstructorTestingTNewFoo) *Foo { - mock := &Foo{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} -` cfg := GeneratorConfig{InPackage: false, ReplaceType: []string{ "github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/pkg/fixtures/example_project/baz.Baz", }} - s.checkGenerationWithConfig("example_project/baz/foo.go", "Foo", cfg, expected) + s.checkGenerationRegexWithConfig("example_project/baz/foo.go", "Foo", cfg, []regexpExpected{ + // func (_m *Foo) GetBaz() (*baz.Baz, error) + {true, regexp.MustCompile(`func \([^\)]+\) GetBaz\(\) \(\*baz\.Baz`)}, + // func (_m *Foo) GetBaz() (*foo.InternalBaz, error) + {false, regexp.MustCompile(`func \([^\)]+\) GetBaz\(\) \(\*foo\.InternalBaz`)}, + }) } func (s *GeneratorSuite) TestGenericGenerator() {