Using captured arguments in a thenReturn clause

1,521 views
Skip to first unread message

pham...@thoughtworks.com

unread,
Sep 2, 2013, 3:50:34 PM9/2/13
to moc...@googlegroups.com
I read that ArgumentCaptor<T> is really only for use with verify steps.  What if it wasn't though:

ArgumentCaptor<QueryString> qsCaptor 
    = ArgumentCaptor.forClass(QueryString.class);
ArgumentConsumerFactory<WebResponse> wrFactory 
    = ArgumentConsumerFactory.forClass(WebResponse.class);

when(incomingWebRequest.post(eq("/shineMyShoes.do"), 
        qsCaptor.capture())
        .thenReturn(wrFactory.make(new WRFactory(), qsCaptor));
        // that's a varargs of multiple captors in wrFactory.make(..)

public static class WRFactory {
    // there's no interface contract here, just the method 
    // name "make" and a compatible return type.
    public WebResponse make(QueryString qs) {
       String color = qs.get("polishColor")
       return ... // something using the color.
    }
}

In this instance the type 'QueryString' was enough, but there could be more fine tuned arg matching (injection) based on annotations or http://paranamer.codehaus.org/

Thoughts?

- Paul


David Wallace

unread,
Sep 2, 2013, 5:13:33 PM9/2/13
to moc...@googlegroups.com
I don't see how this could possibly work.  Your call to make is happening when you set up the mock (i.e. at "Arrange" time), but you actually want it to run when your test method is called (i.e., at "Act" time).


--
You received this message because you are subscribed to the Google Groups "mockito" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mockito+u...@googlegroups.com.
To post to this group, send email to moc...@googlegroups.com.
Visit this group at http://groups.google.com/group/mockito.
For more options, visit https://groups.google.com/groups/opt_out.

Szczepan Faber

unread,
Sep 2, 2013, 5:25:38 PM9/2/13
to moc...@googlegroups.com
Not sure what's the exact use case, but why cannot you use the
thenAnswer api? When you implement an Answer, you can get hold of the
arguments (aka 'captured' arguments).

Cheers!
> --
> You received this message because you are subscribed to the Google Groups
> "mockito" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to mockito+u...@googlegroups.com.
> To post to this group, send email to moc...@googlegroups.com.
> Visit this group at http://groups.google.com/group/mockito.
> For more options, visit https://groups.google.com/groups/opt_out.



--
Szczepan Faber
core dev@gradle; lead@mockito
Join me at the Gradle eXchange 2013, Oct 28th in London:
http://skillsmatter.com/event/java-jee/gradle-exchange-2013

pham...@thoughtworks.com

unread,
Sep 4, 2013, 2:34:50 PM9/4/13
to moc...@googlegroups.com
You'e quite right David. A brain fart meant I was thinking in JS style functions rather than Java method invocations.

pham...@thoughtworks.com

unread,
Sep 4, 2013, 7:59:43 PM9/4/13
to moc...@googlegroups.com
Actually, I had a brain-fart in my response to you David too.  There are two 'make' methods that are separate, and one of them happens at time of execution, not time of mock setup.

Anyway, building on Szczepan's suggestion to look into thenAnswer, and finding StubbingWithCustomAnswerTest.shouldAnswer(), I've made a modest adaptor to leverage Paranamer found bits and pieces:

    @Test
    public void shouldAnswerUsingParanamerMagic() throws Exception {
        when(mock.simpleMethod(anyString(), anyInt(), anyInt(), anyInt(), anyInt())).thenAnswer(new ParanamerAnswer<String>() {
            public String inject(InvocationOnMock invocation, String one, Integer two, Integer three, Integer four, Integer five) throws Throwable {
                return invocation.getMethod().getName() + "-" + five + "-" + four + "-" + three + "-" + two + "-" + one;
            }
        });

        assertEquals("simpleMethod-55-44-33-22-test", mock.simpleMethod("test", 22, 33, 44, 55));
    }

    public abstract static class ParanamerAnswer<T> implements Answer<T> {
        public final T answer(InvocationOnMock invocation) throws Throwable {
            Paranamer paranamer = new DefaultParanamer();
            String[] invocationNames = paranamer.lookupParameterNames(invocation.getMethod());
            Object[] invocationArgs = invocation.getArguments();
            Method[] methods = ParanamerAnswer.this.getClass().getMethods();
            for (Method method : methods) {
                if (method.getName().equals("inject")) {
                    String[] injectionNames = paranamer.lookupParameterNames(method);
                    Object[] injectees = new Object[injectionNames.length];
                    injectees[0] = invocation;
                    for (int j = 0; j < injectionNames.length; j++) {
                        String injectionName = injectionNames[j];
                        for (int k = 0; k < invocationNames.length; k++) {
                            String invocationName = invocationNames[k];
                            if (invocationName.equals(injectionName)) {
                                injectees[j] = invocationArgs[k];
                                break;
                            }
                        }
                    }
                    method.invoke(this, injectees);
                    break;
                }
            }
            throw new RuntimeException("no 'inject' method found");
        }
    }


Notes
=====

Paranamer is at - http://paranamer.codehaus.org - and is a 2007 technology to add paranamer name access to Java 5,6,7.  Java 8 obsoletes it. I was in the Java6 committee and not happy that parameter name access was de-scoped.  It's a no-brainer in the post Rails age.

Parameter names are available in the debug tables of classes, but sadly not interfaces.  BytecodeReadingParanamer allows us to easily access the debug-table data for classes (but again, not for interfaces).  DefaultParanamer requires a post-compilation step that uses QDox to parse source (again), then decorate classes just build with an extra static/final string that is the parameter name info for the classes (and interfaces) processes.  This is where you guys might think it's "too much" for inclusion into Mockito. We made an Ant and a Maven capability for that that (that share code obviously)

Nat Pryce was asking the same questions in '08/'09 for Paranamer in respect of interface method parm names, in respect of JMock.

- Paul
 
Reply all
Reply to author
Forward
0 new messages