-
Notifications
You must be signed in to change notification settings - Fork 408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggested improvement around variadic functions #541
Comments
This is a great proposal, thank you for the thought that went into this. There are a few thoughts I have on this:
Could you confirm what the behavior of the Expecter methods is? |
The expecters need some work as well. The main difference is that you wouldn't need to wrap I'm not really sure how you plan to force people to use them, though. All of the "legacy style" is just part of testify.Mock, not mockery itself. If you are going to completely hide the presence of testify.Mock features, you may as well not wrap that struct at all, and just make mockery a standalone package. |
Here's what the expecter calls would look like for a variadic method: func (_e *SampleInterface_Expecter) Sprintf(msg interface{}, args ...interface{}) *SampleInterface_Sprintf_Call {
var vararg interface{} = args
if len(args) == 1 && args[0] == mock.Anything {
vararg = args[0]
}
return &SampleInterface_Sprintf_Call{Call: _e.mock.On("Sprintf", msg, vararg)}
} With that in place I was able to rewrite the tests so all the calls to func TestVariadicWrappersUsingExpecters(t *testing.T) {
m := new(mocks.SampleInterface)
m.EXPECT().Sprintf("specific", "arguments").Return("Boo!")
m.EXPECT().Sprintf(mock.Anything, mock.Anything).Return("Yay!")
// deliberately passing in more than one arguments to the variadic parameter
// this is what fails today, even though we mocked with mock.Anything
s := m.Sprintf("I am a %s %s", "test", "two")
assert.Equal(t, "Yay!", s)
// Making sure things still work correctly with a non-variadic signature
m.EXPECT().Test("something").Return("Yay!")
s = m.Test("something")
assert.Equal(t, "Yay!", s)
m.AssertCalled(t, "Test", "something")
m.AssertCalled(t, "Sprintf", "I am a %s %s", "test", "two")
// Also making sure calling with ZERO arguments to the variadic parameter works
m.Sprintf("bagel")
m.AssertCalled(t, "Sprintf", "bagel")
m.AssertNotCalled(t, "Sprintf", "bagel", "with", "cream", "cheese")
m.AssertNotCalled(t, "Sprintf", "donut")
// Making sure the matchers are still working when we're not using mock.Anything
s = m.Sprintf("specific", "arguments")
assert.Equal(t, "Boo!", s)
m.EXPECT().OneVariadic(mock.Anything).Return()
m.OneVariadic(1, 2, 3, 4, 5)
m.AssertCalled(t, "OneVariadic", 1, 2, 3, 4, 5)
} I had a thought this morning on how I can break this a bit (right now the fact that my variadic parameters are all |
It's not that I will force people to use the expecters, but mockery in v3 will always add them and the project will officially recommend only using expecters. We'll still allow accessing the testify Mock object, but I don't want to add additional wrappers around it if there's a way we can get it to work with what already exists, if that makes sense? So I guess for your proposal, that would only axe the |
Your call. If this feature would be part of a 2.x release then the On wrapper should be part of it; you can always take it out later for v3. |
I spent some time tinkering with this tonight. It's a little bit more invasive than I had originally thought. Some of the tests currently in mockery's suite just won't work anymore, because they completely depend on the old "unroll" technique and how it passed things to the underlying testify Mock. Namely, you can't use You can see the WIP for this at https://github.com/dlwyatt/mockery/pull/1/files if you're interested. I used the #538 branch as a starting point. |
I've submitted a PR to testify (stretchr/testify#1348) which will allow these new features to be added with fewer breaking changes to mockery behavior. We'll see if they accept it; my branch is currently pointed at my fork of testify for demonstration purposes. Assuming they merge my PR and cut a new release, then right now the only breaking change left in https://github.com/dlwyatt/mockery/pull/1/files is the ability to call the On / AssertCalled / AssertNotCalled methods and pass in the raw slice for the variadic parameter, instead of using the more natural syntax and letting mockery roll the arguments up onto a slice for you. I could add some logic to detect that and handle it, but there could be some really wonky corner cases where that winds up doing the wrong thing (where someone actually wants a slice containing another slice, something like that.) Pretty unlikely to come up in test code, but not impossible. |
I've been looking over mockery's history behind this topic. Back in 2017, there was #123 , which introduced behavior to "unroll" variadic arguments when calls were made to the mock. You can read the PR for more details, but the gist of it was, people wanted to be able to write
mock.On("MethodName", 1,2,3,4,5)
instead ofmock.On("MethodName", []interface{}{1,2,3,4,5})
. The change in that PR did allow for that syntax to work, but the problem was that it broke the ability to pass a single value ofmock.Anything
to cover any number of arguments to the variadic parameter.In #359, the
--unroll-variadic=false
flag was added to restore the original behavior, for people who really wanted that singlemock.Anything
to work, and didn't care about losing the other syntax.What I'm proposing here may be a "best of both worlds" approach. The tests I'll list here are all working, and if anyone can think of other use cases that I haven't thrown at it yet, I'll include those as well. Essentially it inverts the approach used in #123 ; instead of "unrolling" the variadic arguments when the mocked function is called, instead "roll them up" in new wrappers around the
On
,AssertCalled
andAssertNotCalled
methods of a testify Mock. This means that you still get the nice syntax sugar ofmock.On("Method", 1,2,3,4,5)
without having to put things into slices yourself, and a singlemock.Anything
parameter will still match any number (zero or more) arguments passed to the variadic parameter. Here's the interface I worked against:I deliberately included one non-variadic method signature in there to make sure those still work properly. My test code looks like this:
You can see how I'm running through the paces of both variadic and non-variadic method signatures, and mocks that use mock.Anything or pass other values in. Mocks generated with today's code (whether
--unroll-variadic
was set to true or false) will all fail. But with this code, the tests all pass:The significant changes from what mockery generates today are:
--unroll-variadic
were set to false; the arguments are passed straight on tomock.Called
On
,AssertCalled
, andAssertNotCalled
. These all call the underlying testify methods after first calling a newrollVariadic
helper function.rollVariadic
function itself is static and would be the same in every generated mock file.rollVariadic
know about the expected parameters associated with each method name. This function is dynamic, but easily generated based only on a list of method names in the mock.If you like this approach, I'm happy to create a PR that will update the generator to actually produce this new code. The
--unroll-variadic
flag would be deprecated and have no further effect on the output, at that point.The text was updated successfully, but these errors were encountered: