-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Allow dynamic returns based on arguments #350
Comments
That example would not build since the func isn't returning anything. Also, you can do: myMock.On("Load", mock.MatchedBy(func (token string) bool { return !isValid(token) }).Return(nil, errors.New("invalid")) |
@ernesto-jimenez I fixed the example. I'll try your example. |
@ernesto-jimenez the thing is, I need to return some value based on input. I could use For this case there's almost no good way to return data based on input. |
@alexandrevicenzi I'm struggling to see the use case. Why do you need some logic within that method? If you are worried about asserting the mock is getting a valid token, you could do: myMock.On("Load", mock.AnythingOfType("string")).Run(func(args mock.Arguments) { {
assert.Equal(true, isValid(args.Get(0).(string)), "valid token expected")
}) Our mocks package currently cares only about inputs and outputs, rather than building custom outputs based on your own logic. If you want to have more complex mocks, you can build them easily and even leverage this package. Here is a quick example: type yourCustomStruct struct {
yourMock
}
func (s *yourCustomStruct) Load(token string) (*MyObj, error) {
s.yourMock.Called(token) // <- this is assuming you want to do myMock.On("Load", ...) and then assert it has been called
if isValid(token) {
return someStuff(), nil
} else {
return nil, errors.New("Oh!")
}
}
|
@ernesto-jimenez Yes, this works and it's reusable. But what if this logic must change in some scenario? I know that I could create many I was just thinking that this feature could be a good hack somewhere, for me it would be more easy to make a quick setup rather than creating multiples |
if you want different behaviors you can have different structs, or you could have the struct take a function: type yourCustomStruct struct {
yourMock
loadFn func(string) (*MyObj, error)
}
func (s *yourCustomStruct) Load(token string) (*MyObj, error) {
s.yourMock.Called(token) // <- this is assuming you want to do myMock.On("Load", ...) and then assert it has been called
return s.loadFn(token)
} |
@alexandrevicenzi looks like it's solved here https://github.com/vektra/mockery#return-value-provider-functions |
@alexandrevicenzi @ernesto-jimenez I am trying to mock a file system like object on which I want to return some data on first call and return EOF on second call. Now to achieve this I have to write custom function in the mock object to track the no. of function calls which is already done by |
In addition to the above reasons, I'd like to see this feature added so that I can mock an API returning a httpClient := new(mockHttpClient)
httpClient.On(
"Do",
mock.MatchedBy(func(req *http.Request) bool {
// Do some logic to accept the request
return true
}
).Return(
&http.Response{
StatusCode: 200,
Body: ioutil.NopCloser(strings.NewReader("response data")),
},
nil,
).Times(10) The problem here is that the Let's assume that the Following @ernesto-jimenez's workaround is sufficient, but a baked-in solution would be nice. |
Almost any other mocking framework (even some golang ones) allows for this, what is the problem of having it also in this one? |
I am also stuck here, I am trying to test a function which tries to push to the queue and tries 5 times, I want the mocked queue to return error first 3 times and success just after that. how can we achieve this with current implementation? |
This snippet is slightly hacky, but works in existing testify API:
|
this was supported by testify already, it's RunFn Line 67 in 858f37f
sample code
|
@hieuvo |
@andreib1 |
It's frustrating this hasn't been officially implemented, as it is a real pain to mock tests that fail for intermittent calls. Examples where this is needed are filesystem and http. I have a retry code in my application, and making my own mocks that emulate the whole filesystem or http server miss the point of having a mocking framework. |
@andreib1 @brevno , I was in the same situation as us guys but I think @rayzyar pointed at the actual solution. As you stated, the problem is testing: I tried the hack mention, but as told, It wasn't concurrent safe so then I tested using It actually worked, implementing the function but in I'm using testify version 1.4.0 |
If you came across this post like i did, and all you need to do is change the call return after n calls, try the answer from: Use the
Not sure if this addresses above concerns on concurrency, but i assume it would if all you care about is to return a call with x after n times, then return y after. |
Before I found this issue just now, I had come up with something not unlike one of the solutions above.
This code just shows the principle, the details are elided. Not pretty, but in my particular case much better than the alternative of lots of repetitive code. Concurrency (the possibility that two threads might call MyFunc around the same time) wasn't an issue in my case. Tthis problem should at least have how-to documentation in testify, i.e., other than in an issue: users expect the facility to be there and are wasting time searching for something that doesn't exist instead of stepping outside testify to solve it, as Ernesto demonstrates. |
This is a 4 year old issue and the original maintainers are gone. I'm happy to take a serious look at this but from a quick scan, I think I see differing problem statements and differing solution suggestions. The main thing I think I see is people asking for dynamic return values based on the original arguments... is that assesement correct? (I know, it's been 4 years and it's probably frustrating but we're trying to get the backlog smaller and specifically cleaned up 😄 ) |
Thank you, Martijn. I'm a new user, but your assessment is correct. From my point of view, although it wasn't what I did, ernesto-jimenez's solution of a wrapper (with a call to Called()) looks like the most appropriate way to handle it without changing the existing API, so I think it would suffice to document that, with a link to it from the documentation for Call.Return(). Alternatively, a variation of Run() (RunReturn()?) could be added to the API, the return value of which becomes the return value of the call. But then you have to have document how, if that's defined, it takes priority over a Return() call, or maybe whichever was called last takes priority, and there may be other issues. A more general Return() might have been better from the beginning, but the complication of adding an alternative mechanism to the API now may not be justified. |
Dynamic/computed returns can be achieved without adding a new feature by doing something like this: type mockClient struct {
mock.Mock
}
// Perfectly normal testify mock method implementation
func (m *mockClient) Write(context.Context, lib.WriteRequest) (*lib.WriteResponse, error) {
args := m.Called(ctx)
return args.Get(0).(*WriteResponse), args.Error(1)
}
// Any function having the same signature as client.Write()
type WriteFn func(context.Context, lib.WriteRequest) (*lib.WriteResponse, error)
// Expect a call to the mock's Write(ctx, req) method where the return value
// is computed by the given function
func (m *mockClient) OnWrite(ctx, req interface{}, impl WriteFn) *mock.Call {
call := m.On("Write", ctx, req)
call.Run(func(CallArgs mock.Arguments) {
callCtx := CallArgs.Get(0).(context.Context)
callReq := CallArgs.Get(1).(lib.WriteRequest)
call.Return(impl(callCtx, callReq))
})
return call
} and then consume it in the test: func TestThing(t *testing.T){
t.Run("write", func(t *testing.T){
client := mockClient{}
client.Test(t)
thing := Thing{Client: &client}
// Keeps track of values written to mock client
records := []lib.Record{}
ctx := mock.Anything
req := lib.WriteRequest{Value: "expected value"}
// If the request doesn't match, the mock impl is not called and the test fails
client.OnWrite(ctx, req, func(_ context.Context, req lib.WriteRequest) (*lib.WriteResponse, error){
id := len(records) // ID is computed
records = append(records, lib.Record{ID: id, Value: req.Value})
return &lib.WriteResponse{ID: id}, nil
}).Once()
thing.DoSomethingTestable("expected value")
client.AssertExpectations(t)
assert.Contains(t, records, lib.Record{ID: 0, Value: "expected value"})
})
} |
I would like to emphasize that this type of functionality is considered by many to be bare minimum functionality. |
Why this is such a big problem to implement this? |
I also just encountered a use case for this, and find it very frustrating that this isn't implemented. |
For anyone who is willing to try a different mocking package to get this kind of functionality, I recently stumbled upon: Which perfectly fit this use case. As an added benefit, most of the developer facing API is generated in with strong types :). |
I see #742 was opened to address this. Seems to be blocked waiting for a review by a maintainer? |
Got this working using multiple function returns: First, declaring my mock: func (g *testGitHubAPI) FilesContent(ctx context.Context, owner string, repo string, branch string, filepaths []string) (github.FileContentByPath, error) {
args := g.Called(ctx, owner, repo, branch, filepaths)
// Return functions instead of values to allow custom results based on inputs
return args.Get(0).(func(ctx context.Context, owner, repo, branch string, filepaths []string) github.FileContentByPath)(ctx, owner, repo, branch, filepaths),
args.Get(1).(func(ctx context.Context, owner, repo, branch string, filepaths []string) error)(ctx, owner, repo, branch, filepaths)
} Then by returning multiple returns based on the input to the functions, note the one function per return value: ghAPI := &testGitHubAPI{}
ghAPI.On("FilesContent", mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("[]string")).
Return(
// One function return per return value is required
func(ctx context.Context, owner, repo, branch string, filepaths []string) github.FileContentByPath {
if len(filepaths) > 0 && filepaths[0] == "a.json" {
return github.FileContentByPath{
"a.json": `{"name": "a"}`,
}
} else if len(filepaths) > 0 && filepaths[0] == "b.json" {
return github.FileContentByPath{
"b.json": `{"name": "b"}`,
}
}
return github.FileContentByPath{}
},
func(ctx context.Context, owner string, repo string, branch string, filepaths []string) error {
return nil
}) |
I've got it by some code in method: func (m *MockObject) Get(data int) int {
args := m.Called(data)
return args.Int(0)
} Use this: func (m *MockObject) Get(data int) int {
args := m.Called(data)
type methodSignature = func(int) int
switch args.Get(0).(type) {
case methodSignature:
return args.Get(0).(methodSignature)(data)
default:
return args.Int(0)
}
} In test you can use it as you want: mockObject := new(MockObject)
mockObject.On("Get", 1).Return(1)
mockObject.On("Get", mock.Anything).Return(func(data int) int {
return data - 1
}) |
@mvdkleijn What's the status of this? This would be pretty simple to implement by adding a property to Call but there are already 150 outstanding PRs, many of which are months or years old. So even if the maintainers are ok with this change, I'm not optimistic that it would get merged. |
Seems like testify is dead code as of right now. I also ran into this issue and used the hackish solutions described above. Guess the hack is the "official" solution as the officials maintainers are gone. |
As Alex mentioned it works very well. It's just a code generator that works with tesitfy. Run it with
|
The solution documented above and implimented in mockery is in my opinion sufficient for those who want to create mixed mocks and fakes. |
C# Moq allows to return data based on call arguments.
testify/mock
don't. It's possible to do usingRun
, but it's a big mess of code.It would be great to have something like this:
I can send a PR if someone like this idea.
The text was updated successfully, but these errors were encountered: