Skip to content

Latest commit

 

History

History
371 lines (267 loc) · 12.5 KB

features.md

File metadata and controls

371 lines (267 loc) · 12.5 KB

Features

Replace Types

:octicons-tag-24: v2.23.0

The replace-type parameter allows adding a list of type replacements to be made in package and/or type names. This can help overcome issues like usage of type aliases that point to internal packages.

The format of the parameter is:

originalPackagePath.originalTypeName=newPackageName:newPackagePath.newTypeName

For example:

mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz=baz:github.com/vektra/mockery/v2/baz.Baz

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:

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.

mockery --replace-type cloud.google.com/go/internal/pubsub=cloud.google.com/go/pubsub

Original source:

import (
    "cloud.google.com/go/pubsub"
)

type Handler struct {
    HandleMessage(m pubsub.Message) error
}

Invalid mock generated without this parameter (points to an internal folder):

import (
    mock "github.com/stretchr/testify/mock"

    pubsub "cloud.google.com/go/internal/pubsub"
)

func (_m *Handler) HandleMessage(m pubsub.Message) error {
    // ...
    return nil
}

Correct mock generated with this parameter.

import (
    mock "github.com/stretchr/testify/mock"

    pubsub "cloud.google.com/go/pubsub"
)

func (_m *Handler) HandleMessage(m pubsub.Message) error {
    // ...
    return nil
}

Generic type constraints can also be replaced by targeting the changed parameter with the square bracket notation on the left-hand side.

mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz[T]=github.com/vektra/mockery/v2/baz.Baz

For example:

type InternalBaz[T any] struct{}

func (*InternalBaz[T]) Foo() T {}

// Becomes
type InternalBaz[T baz.Baz] struct{}

func (*InternalBaz[T]) Foo() T {}

If a type constraint needs to be removed and replaced with a type, target the constraint with square brackets and include a '-' in front to have it removed.

mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz[-T]=github.com/vektra/mockery/v2/baz.Baz

For example:

type InternalBaz[T any] struct{}

func (*InternalBaz[T]) Foo() T {}

// Becomes
type InternalBaz struct{}

func (*InternalBaz) Foo() baz.Baz {}

When replacing a generic constraint, you can replace the type with a pointer by adding a '*' before the output type name.

mockery --replace-type github.com/vektra/mockery/v2/baz/internal/foo.InternalBaz[-T]=github.com/vektra/mockery/v2/baz.*Baz

For example:

type InternalBaz[T any] struct{}

func (*InternalBaz[T]) Foo() T {}

// Becomes
type InternalBaz struct{}

func (*InternalBaz) Foo() *baz.Baz {}

packages configuration

:octicons-tag-24: v2.21.0

!!! info See the Migration Docs on how to migrate to this new feature.

Mockery has a configuration parameter called packages. In this config section, you define the packages and the interfaces you want mocks generated for. The packages can be any arbitrary package, either your own project or anything within the Go ecosystem. You may provide package-level or interface-level overrides to the default config you provide.

Usage of the packages config section is desirable for multiple reasons:

  1. Up to 5x increase in mock generation speed over the legacy method
  2. Granular control over interface generation, location, and file names
  3. Singular location for all config, instead of spread around by //go:generate statements
  4. Clean, easy to understand.

Examples

Here is an example configuration set:

with-expecter: True
packages:
  github.com/vektra/mockery/v2/pkg: # (1)!
    interfaces:
      TypesPackage:
      RequesterVariadic:
        config: # (2)!
          with-expecter: False 
        configs:
          - mockname: MockRequesterVariadicOneArgument
            unroll-variadic: False
          - mockname: MockRequesterVariadic
  io:
    config:
      all: True # (3)!
    interfaces:
      Writer:
        config:
          with-expecter: False # (4)!
  1. For this package, we provide no package-level config (which means we inherit the defaults at the top-level). Since our default of all: is False, mockery will only generate the interfaces we specify. We tell it which interface to generate by using the interfaces section and specifying an empty map, one for each interface.
  2. There might be cases where you want multiple mocks generated from the same interface. To do this, you can define a default config section for the interface, and further configs (plural) section, one for each mock. You must specify a mockname for the mocks in this section to differentiate them.
  3. This is telling mockery to generate all interfaces in the io package.
  4. We can provide interface-specific overrides to the generation config.

Templated variables

!!! note Templated variables are only available when using the packages config feature.

Included with this feature is the ability to use templated strings for various configuration options. This is useful to define where your mocks are placed and how to name them. You can view the template variables available in the Configuration section of the docs.

Recursive package discovery

:octicons-tag-24: v2.25.0

When #!yaml recursive: true is set on a particular package:

packages:
  github.com/user/project:
    config:
      recursive: true
      with-expecter: true

mockery will dynamically discover all sub-packages within the specified package. This is done by calling packages.Load on the specified package, which induces Go to download the package from the internet (or simply your local project). Mockery then recursively discovers all sub-directories from the root package that also contain .go files and injects the respective package path into the config map as if you had specified them manually. As an example, your in-memory config map may end up looking like this:

packages:
  github.com/user/project:
    config:
      recursive: true
      with-expecter: true
  github.com/user/project/subpkg1:
    config:
      recursive: true
      with-expecter: true
  github.com/user/project/subpkg2:
    config:
      recursive: true
      with-expecter: true

You can use the showconfig command to see the config mockery injects. The output of showconfig theoretically could be copy-pasted into your YAML file as it is semantically equivalent.

mockery will not recurse into submodules, i.e. any subdirectory that contains a go.mod file. You must specify the submodule as a separate line item in the config if you would like mocks generated for it as well.

??? note "performance characteristics" The performance when using #!yaml recursive: true may be worse than manually specifying all packages statically in the YAML file. This is because of the fact that mockery has to recursively walk the filesystem path that contains the package in question. It may unnecessarily walk down unrelated paths (for example, a Python virtual environment that is in the same path as your package). For this reason, it is recommended not to use #!yaml recursive: true if it can be avoided.

Regex matching

You can filter matched interfaces using the include-regex option. To generate mocks only for interfaces ending in Client we can use the following configuration:

packages:
  github.com/user/project:
    config:
      recursive: true
      include-regex: ".*Client"

To further refine matched interfaces, you can also use exclude-regex. If an interface matches both include-regex and exclude-regex then it will not be generated. For example, to generate all interfaces except those ending in Func:

packages:
  github.com/user/project:
    config:
      recursive: true
      include-regex: ".*"
      exclude-regex: ".*Func"

You can only use exclude-regex with include-regex. If set by itself, exclude-regex has no effect.

??? note "all: true" Using all: true will override include-regex (and exclude-regex) and issue a warning.

Mock Constructors

:octicons-tag-24: v2.11.0

All mock objects have constructor functions. These constructors do basic test setup so that the expectations you set in the code are asserted before the test exits.

Previously something like this would need to be done:

factory := &mocks.Factory{}
factory.Test(t) // so that mock does not panic when a method is unexpected
defer factory.AssertExpectations(t)

Instead, you may simply use the constructor:

factory := mocks.NewFactory(t)

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.

Expecter Structs

:octicons-tag-24: v2.10.0 · with-expecter: True

Mockery now supports an "expecter" struct, which allows your tests to use type-safe methods to generate call expectations. When enabled through the with-expecter: True mockery configuration, you can enter into the expecter interface by simply calling .EXPECT() on your mock object.

For example, given an interface such as

type Requester interface {
	Get(path string) (string, error)
}

You can use the expecter interface as such:

requesterMock := mocks.NewRequester(t)
requesterMock.EXPECT().Get("some path").Return("result", nil)

A RunAndReturn method is also available on the expecter struct that allows you to dynamically set a return value based on the input to the mock's call.

requesterMock.EXPECT().
	Get(mock.Anything).
	RunAndReturn(func(path string) (string, error) { 
		fmt.Println(path, "was called")
		return ("result for " + path), nil
	})

!!! note

Note that the types of the arguments on the `EXPECT` methods are `interface{}`, not the actual type of your interface. The reason for this is that you may want to pass `mock.Any` as an argument, which means that the argument you pass may be an arbitrary type. The types are still provided in the expecter method docstrings.

Return Value Providers

:octicons-tag-24: v2.20.0

Return Value Providers can be used one of two ways. You may either define a single function with the exact same signature (number and type of input and return parameters) and pass that as a single value to Return, or you may pass multiple values to Return (one for each return parameter of the mocked function.) If you are using the second form, for each of the return values of the mocked function, Return needs a function which takes the same arguments as the mocked function, and returns one of the return values. For example, if the return argument signature of passthrough in the above example was instead (string, error) in the interface, Return would also need a second function argument to define the error value:

type Proxy interface {
passthrough(ctx context.Context, s string) (string, error)
}

First form:

proxyMock := mocks.NewProxy(t)
proxyMock.On("passthrough", mock.AnythingOfType("context.Context"), mock.AnythingOfType("string")).
Return(
    func(ctx context.Context, s string) (string, error) {
        return s, nil
    }
)

Second form:

proxyMock := mocks.NewProxy(t)
proxyMock.On("passthrough", mock.AnythingOfType("context.Context"), mock.AnythingOfType("string")).
Return(
    func(ctx context.Context, s string) string {
        return s
    },
    func(ctx context.Context, s string) error {
        return nil
    },
)