Skip to content

Migrating to Packages

The packages feature is a new configuration scheme that aims to simplify and improve a lot of legacy behavior. This will be the only way to generate mocks in v3. These docs outline general principals for migrating to the new scheme.

Background

mockery was built during the pre-module era of Golang. Much of its codebase and configuration syntax was designed around file-based operations. This model became highly inefficient once Golang migrated to module-based packages. The old configuration semantics also proved limiting -- many users introduced and requested feature additions to mockery to support esoteric use-cases. This proved to be a huge maintenance burden that existed solely because the configuration model could not flexibly describe all the situations users wanted. The packages semantics provides us a few highly desirable traits:

  1. Orders of magnitude performance increase, due to calling packages.Load once or twice for an entire project, versus once per file in the legacy semantics.
  2. Hierarchical configuration model that allows interface-specific config to be inherited from package-level config, which is inherited from defaults.
  3. Single configuration file that describes the entirety of mockery's behavior, instead of spread out by //go:generate statements.
  4. Extensive and flexible usage of a Golang string templating environment that allows users to dynamically specify parameter values.

Configuration Changes

The existence of the packages: map in your configuration acts as a feature flag that enables the feature.

The configuration parameters used in packages should be considered to have no relation to their meanings in the legacy scheme. It is recommended to wipe out all previous configuration and command-line parameters previously used.

The configuration docs show the parameters that are available for use in the packages scheme. You should only use the parameters shown in this section. Mockery will not prevent you from using the legacy parameter set, but doing so will result in undefined behavior.

All of the parameters in the config section can be specified at the top level of the config file, which serves as the default values. The packages config section defines package-specific config. See some examples here.

Examples

Separate mocks/ directory

Take for example a configuration where you are specifying all: true at the top of your repo and you're placing your mocks in a separate mocks/ directory, mirroring the directory structure of your original repo.

YAML
testonly: False
with-expecter: True
keeptree: True
all: True

The equivalent config for packages looks like this:

YAML
with-expecter: True
inpackage: True
dir: mocks/{{ replaceAll .InterfaceDirRelative "internal" "internal_" }} #(1)!
mockname: "{{.InterfaceName}}"
outpkg: "{{.PackageName}}"
filename: "{{.InterfaceName}}.go"
all: True
packages:
  github.com/org/repo:
    config:
      recursive: True
  1. The use of replaceAll is a trick that is done to ensure mocks created for internal packages can be imported outside of the mock directory. This retains the behavior of the legacy config.

While the example config provided here is more verbose, that is because we're specifying many non-default values in order to retain strict equivalence for this example. It's recommended to refer to the configuration parameters to see the defaults provided.

Adjacent to interface

Another common pattern in the legacy config is to place mocks next to the file that defined the interface.

YAML
with-expecter: True
inpackage: True
all: True

For example, the mock file would be laid out like:

Text Only
./getter.go
./mock_Getter.go

The equivalent config would look like:

YAML
with-expecter: True
inpackage: True
dir: "{{.InterfaceDir}}"
mockname: "Mock{{.InterfaceName}}"
outpkg: "{{.PackageName}}"
filename: "mock_{{.InterfaceName}}.go"
all: True
packages:
  github.com/org/repo:
    config:
      recursive: True

//go:generate directives

Previously, the recommended way of generating mocks was to call mockery once per interface using //go:generate. Generating interface-specific mocks this way is no longer supported. You may still use //go:generate to call mockery, however it will generate all interfaces defined in your config file. There currently exists no semantics to specify the generation of specific interfaces from the command line (not because we reject the idea, but because it was not seen as a requirement for the initial iteration of packages).

Behavior Changes

The legacy behavior iterated over every .go file in your project, called packages.Load to parse the syntax tree, and generated mocks for every interface found in the file. The new behavior instead simply grabs the list of packages to load from the config file, or in the case of recursive: True, walks the filesystem tree to discover the packages that exist (without actually parsing the files). Using this list, it calls packages.Load once with the list of packages that were discovered.

Filesystem Tree Layouts

The legacy config provided the inpackage parameter which, if inpackage: True, would place the mocks in the same package as your interfaces. Otherwise, it would place it in a separate directory.

These two layouts are supported in the packages scheme. See the relevant docs here.