mock icon indicating copy to clipboard operation
mock copied to clipboard

Add argument captors

Open tylersammann opened this issue 6 years ago • 9 comments

Description

I think it would be nice to build an ArgumentCaptor interface and argumentCaptor struct that extends Matcher through composition, but store the value of the arguments before the "match" step. Those argument values could then be retrieved for later assertions (very close to how Mockito's ArgumentCaptors work in Java) https://static.javadoc.io/org.mockito/mockito-core/2.6.9/org/mockito/ArgumentCaptor.html

Use Case

I recently ran into a situation where I wanted to write a test that asserted something about the contents of an argument (a slice of struct pointers) passed to a mocked method. Using matchers as they currently exist (i.e. eqMatcher), I couldn't find a good way to do that without already knowing the memory addresses of the pointers in the slice.

tylersammann avatar Feb 07 '19 16:02 tylersammann

Have you looked at the gomock.Do() (docs)? It seems like this would get you pretty close.

poy avatar Feb 12 '19 16:02 poy

Thanks for the reply @poy! Yes, I started writing my tests with gomock.Do() and it was working ok. However, the methods I was mocking had long complicated signatures, so the anonymous funcs I needed to pass to Do() were very long. The ArgumentCaptors get the same end result with a lot less code in those situations.

tylersammann avatar Feb 12 '19 19:02 tylersammann

I also tried writing a decorator function for passing into Do() that looked something like this.

func ArgumentCaptor(capturedArgs []interface{}) func(args ...interface{}) {
	return func(args ...interface{}) {
		if len(capturedArgs) != len(args) {
			panic(fmt.Sprintf("The capturedArgs array length %d did not match the actual argument count %d",
				len(capturedArgs), len(args)))
		}
		for i, arg := range args {
			capturedArgs[i] = arg
		}
	}
}

It involves writing less code, but its downside was the need to grab the arguments based on their order in the signature with an index.

tylersammann avatar Feb 12 '19 19:02 tylersammann

@tylersammann

Sorry for the slow response. @balshetzer and I are discussing how we would like to approach adding matchers. I'll let you know what decide!

poy avatar Feb 24 '19 04:02 poy

@poy @balshetzer Any news here?

tylersammann avatar Jun 05 '19 14:06 tylersammann

+1

shivasuri avatar Aug 28 '19 17:08 shivasuri

+1

Infiniverse avatar Dec 01 '19 22:12 Infiniverse

@poy @balshetzer

I'm struggling to test the following scenario with Do():

Say my function foo() calls two mocked method .F1() and .F2(), I want to test the argument passed to .F1() is the same as the one passed to .F2()

var arg string
mockStruct.EXPECT()
	.F1(gomock.Any())
	.Do(func(_arg string) { arg = _arg})
mockStruct.EXPECT()
	.F2(gomock.Any(), gomock.Eq(arg))
foo()

This obviously won't work. Any good way to test this right now or the new matchers can help with that?

hgl avatar Jan 05 '20 05:01 hgl

@hgl I think there might be a way to do something similar to this with .Do(), but it would involve storing the arguments used in both F1 and F2 and comparing them afterwards. FWIW, here is how I think I'd do with with my argument captor implementation from this PR https://github.com/golang/mock/pull/263

f1Captor := gomock.AnyCaptor()
f2Captor := gomock.AnyCaptor()

mockStruct.EXPECT().F1(f1Captor)
mockStruct.EXPECT().F2(f2Captor)
foo()

assert.Equal(t, f1Captor.Value(), f2Captor.Value())

tylersammann avatar Feb 11 '20 16:02 tylersammann