RFC: integration of github.com/matryer/moq
-style mocks
#715
Replies: 2 comments 7 replies
-
Hi @LandonTClipp
As a experienced user of
and
I think this critic is debatable as well. As long as there is no need for just an other config file, that pollutes my repository, since everything that is needed to generate the mocks fits into the
In regards to the possible values for the configuration attribute
I wonder, if the generator could be made "generic" in the sense that it just uses Go templates again to generate the final result. Similar to how it is done in https://github.com/hexdigest/gowrap. Basically mockery would just collect the necessary information (state), that is needed to generate the mocks and pass this to the Generator, which then basically loads a Go template where the final result is generated based on the state that is passed. I hope, this thoughts are helpful for you. |
Beta Was this translation helpful? Give feedback.
-
One concern I have with this is that we would be requiring circular dependency between the two packages. Mockery would need to import the implementation of |
Beta Was this translation helpful? Give feedback.
-
matryer/moq#208 was submitted to the maintainers of matryer/moq suggesting integrating the moq-style mocks into mockery. This suggestion seems well-received due to the benefits we could gain from combining the two projects' strengths.
This GitHub issue serves as a design proposal, discussion, and feature tracking page.
PR Tracker
https://github.com/vektra/mockery/pulls?q=is%3Apr+label%3Amoq+
Background
vektra/mockery
style of mocks rely on argument matching behavior provided fromtestify/mock
. Expectations are created by mapping argument values to return values and side effects. Many valid criticisms have been raised about various pitfalls of this kind of expectation system. There are various alternate strategies for defining mock objects, and it's the goal of this project to combine the strengths of these alternate strategies with the strengths implemented in mockery itself.mockery benefits
Speed
Mockery has undergone a huge rework over the last year with the new
packages
feature. Historically, mockery (and basically all other mock implementations) relied heavily on usinggo:generate
statements to generate one mock per instantiation ofmockery
. This was extremely inefficient because it meant thepackages.Load
call required to parse the AST had to parse the package and all of its dependencies many times. This was anO(n*m)
operation (wheren
is the number of mocks being generated, andm
is the number of dependent packages). The rather trivial insight was to simply collect all packages to parse within a single list and pass all of them intopackages.Load
at once, which removedm
from the equation. The new configuration model allows mockery to have this singular list of packages available within a single process.Configuration inheritance
The other benefit of the configuration model is that allows inheritable configuration. Configuration can be specified at the top-level defaults, at the package level, and at the interface level, with parameters being inherited at each level. This is a powerful way to differentiate mock generation based on individual needs.
Templating
The configuration also takes advantage of Go templating, which allows users to call upon variables that are only known at runtime. Templating functions provide more power and flexibility to users to alter template variables.
mockery criticisms
mock.Anything
in mock objects means that the expecter's argument types have to beany
. This means that argument types are not a compile-time safety guarantee but rather runtime.testify
that goes into correctly mapping method calls to their corresponding expectations. While testify does its job perfectly well, its clear that the complicated details of argument-matching necessitates a huge amount of logic to support it. A different mocking model could greatly simplify the generated code.matryer/moq
benefitsSimplicity
These style of mocks provide an incredibly simple way of defining mocks: you define the function to be run for your mock call. No argument matching, no assertions on how the mock was called. This simplicity encourages the programmer to not do too much with the mock, which reduces coupling. It doesn't make it impossible to do nasty things like call count and argument value assertions, but it makes it somewhat harder to do.
Type safety
moq
objects are strictly type safe, which means test compilation will fail immediately if your mock doesn't satisfy the interface no matter what.matryer/moq
criticismsSpeed
Like all other mock projects besides mockery,
moq
relies ongo:generate
to instantiate onemoq
process per mock object. This is highly inefficient for the reasons aforementioned.Configuration model
Its configuration model is entirely CLI parameter based. There are no config files, no inheritable config, no use of Go templating.
Proposal
Configuration syntax
moq-style mocks can be easily called upon in mockery by yaml such as this:
Of course,
style
could be specified anywhere: at the top-level (defaults), in an environment var, in the CLI parameter, at the package level, or at the interface level.Generated code
To maintain backwards compatibility with all existing usage of
moq
, the generated code would be identical to that ofgithub.com/matryer/moq
, minus any necessary differences to comments within the mock file. For those unfamiliar, this provides an example of the mockmoq
generates.Given:
This is generated.
You then define each function individually in your test as such:
File placement
Where the mock files are physically placed will be according to the
dir
parameter, just as in the current system.Code implementation
Proposal 1 (rejected)
Rejected proposal 1 (expand)
mockery has a concept of an [`Outputter`](https://github.com/vektra/mockery/blob/v2.34.0/pkg/outputter.go#L286), which is the object that determines where a mock should physically reside, and a [`Generator`](https://github.com/vektra/mockery/blob/v2.34.0/pkg/generator.go#L85) which creates the mock code. The `Outputter` instantiates a `Generator` which creates the mock in-memory, and then is flushed to the file handle provided by the `Outputter`.This relationship will need to change. The
Outputter
will need to instantiate (or take, as a parameter) aGeneratorV2
(actual name TBD) that parses the interface's AST and provides a set of parameters to some arbitrary Go templating string, the sum of which would provide enough information to generate any arbitrary kind of mock.GeneratorV2
will render the template at the location thatOutputter
specifies using the template string pointed to (by name or by filepath) in thestyle
parameter.This methodology of being driven off of templates grants us an enormous amount of flexibility, as the API simply becomes the set of parameters provided to the Go templating engine. Anyone using
mockery
could then define their own template to create any kind of mock they want.We will need a backwards compatability layer for
mockery
style mocks because it is not 100% driven off of templates, so unfortunatelyGeneratorV2
will only be able to be used onmoq
style mocks for the initial iteration.Proposal 2
A new
GeneratorTemplate
object will be created that does all of the variable-gathering related to the interface in question. The variables gathered will be a similar set that are currently used in mockery'sGenerator
.GeneratorTemplate
will be designed in a way that is totally agnostic to the style of mock being generated. Once the variable gathering work has completed,GeneratorTemplate
will pass this set of variables to a template string, which will call upon this set of variables. The particular template in use will be controlled by thestyle
parameter.moq
already drives its generation off a single template, shown here. The idea is that we can embed this string in the executable and create a mapping of style names to template string. This scheme will allow mockery to take any arbitrary template string, even user-provided ones, which grants us a large amount of flexibility. The mockery project itself will maintain its own small, curated list of mock templates.mockery
-style mocks unfortunately are only partially driven off of templates, so it will not be able to be ported into theGeneratorTemplate
. The code will have to be bifurcated untilmockery
-style mocks can be completely ported to a pure template.packages.Load
changesmoq currently calls
packages.Load
based off filesystem path: https://github.com/matryer/moq/blob/c59089b2cd89d975f3d44924f04f37591fbd701d/internal/registry/registry.go#L31This will need to be modified to integrate with the
Interface
struct that contains the AST information about each interface discovered in the package. This struct is generated using the information returned from here.Beta Was this translation helpful? Give feedback.
All reactions