ssacher-tgm / mockito

Automatically exported from code.google.com/p/mockito
0 stars 0 forks source link

Chaining Expectations #149

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Testing legacy code that accepts factories and framework is a bit tedious, even 
with Mockito, but 
it's not as bad as other frameworks.  The test would need to mock the factory, 
and all the 
intermediate values.

For example, supposed that we have a network class that takes in an SSLFactory 
and has a 
method that writes encrypted data to the output stream of one of the sockets.  
Testing the code 
would look like:

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        SSLSocketFactory factory = mock(SSLSocketFactory.class);

        SSLSocket socket = mock(SSLSocket.class);
        when(socket.getOutputStream()).thenReturn(baos);
        when(factory.createSocket()).thenReturn(socket);
        when(factory.createSocket().getOutputStream()).thenReturn(baos);

        Client client = new Client(factory);
        client.send(message);

        Assert.assertEquals(baos, message);

It would be great if I can write it as:

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        SSLSocketFactory factory = mock(SSLSocketFactory.class);
        //// Note the difference here
        when(factory.createSocket().getOutputStream()).thenReturn(baos);

        Client client = new Client(factory);
        client.send(message);

        Assert.assertEquals(baos, message);

when() os a slightly different method would create mocks for the intermediate 
classes (e.g. 
Socket) here.

Original issue reported on code.google.com by mahm...@notnoop.com on 14 Nov 2009 at 10:31

GoogleCodeExporter commented 8 years ago
Hi,

I'll try to find some time to implement it.

Thanks for reporting!

Original comment by szcze...@gmail.com on 14 Nov 2009 at 11:40

GoogleCodeExporter commented 8 years ago
Thanks.  I just came across the discussion list about "deep stubbing".  Using 
RETURNS_MOCK didn't work for 
me, but this worked:

    private final static Answer<Object> MY_RETURNS_MOCKS = new Answer<Object>() {
        private static final long serialVersionUID = -6926328908792880098L;

        private final HashMap<Class<?>, Object> mocks = new HashMap<Class<?>, Object>();

        public Object answer(InvocationOnMock invocation) throws Throwable {
            Class<?> clz = invocation.getMethod().getReturnType();
            if (mocks.containsKey(clz)) {
                return mocks.get(clz);
            } else {
                Object mock = mock(clz);
                mocks.put(clz, mock);
                return mock;
            }
        }
    };

Needless to say, this requires a bit of testing and error checking (e.g. 
handling final classes).

Original comment by mahm...@notnoop.com on 14 Nov 2009 at 11:47

GoogleCodeExporter commented 8 years ago
Very nice. Thanks for submitting!

Original comment by szcze...@gmail.com on 15 Nov 2009 at 8:30

GoogleCodeExporter commented 8 years ago
Needless to say my code is quite buggy.  I'll try to clean it up.

Original comment by mahm...@notnoop.com on 15 Nov 2009 at 8:44

GoogleCodeExporter commented 8 years ago
I had a need for this in practice, so I adapted your code into this.
It seems to work fairly well but I haven't tested it _that_ much.

public class MockitoUtil {
        public static <T> T deepMock(Class<T> clazz) {
                return Mockito.mock(clazz, new DeepAnswer());
        }
}

class DeepAnswer implements Answer<Object> {
        private static final long serialVersionUID = -6926328908792880098L;

        private final HashMap<Class<?>, Object> mocks = new HashMap<Class<?>, Object>();

        public Object answer(InvocationOnMock invocation) throws Throwable {
                Class<?> clz = invocation.getMethod().getReturnType();
                if (clz.isPrimitive()) {
                        return null;
                }
                if (mocks.containsKey(clz)) {
                        return mocks.get(clz);
                } else {
                        Object mock = Mockito.mock(clz, this);
                        mocks.put(clz, mock);
                        return mock;
                }
        }
}

Original comment by kristofer.karlsson@gmail.com on 17 Nov 2009 at 4:41

GoogleCodeExporter commented 8 years ago
Don't worry too much - we'll clean it up. Unfortunately, you might hit the 
problem
revealed in issue 151 :( However, I'll fix 151 quickly and release it with 1.8.1

Original comment by szcze...@gmail.com on 20 Nov 2009 at 9:28

GoogleCodeExporter commented 8 years ago
Yes, I did run into issue 151, and got stuck; except that I thought it was my 
fault!  Attached is my set of 
smoke tests.  Hope you find them helpful.

When trying to handle invocations of different arguments, what's the proper way 
of comparing invocations?  I 
am referring to the withArguments() and withPatternArguments() test use cases.  
Noticed that 
InvocationOnMock doesn't override .hashCode().

Also, I had a difficult time debugging with the Eclipse debugger.  Mock 
underlying representations and 
mockingProgress and iOngoingStubbings are getting corrupted somehow (their 
values change during the 
exception), with some calls to .toString().  I assume that the debugger makes 
toString() calls, while it is 
stepping through when() argument, so .toString() also gets recorded as part of 
the expectation as well.

Original comment by mahm...@notnoop.com on 20 Nov 2009 at 1:11

Attachments:

GoogleCodeExporter commented 8 years ago
>Yes, I did run into issue 151, and got stuck;

151 is very easy to fix. I even did it already but couldn't check-in due to 
problems
with my hgsubversion setup :)

>When trying to handle invocations of different arguments, what's the proper 
way of
>comparing invocations?

Ouch... That's not easy. I'll write some more later.

>Also, I had a difficult time debugging with the Eclipse debugger.

Yeah, I know what you mean but we have to live with it :] Try not to toString() 
mocks
and it should not be such a hassle.

We can make Invocation implement hashCode() easily if needed.

Original comment by szcze...@gmail.com on 20 Nov 2009 at 1:54

GoogleCodeExporter commented 8 years ago
>comparing invocations?

It's not equals() because sometimes parameters are given via ArgumentMatchers. 
You
know all the eq(), anyObject() stuff. Therefore here's how I compare invocations
internally: 

invocationMatcher.matches(invocation)

We'll look at your implementation sometime soon ;)

Original comment by szcze...@gmail.com on 3 Dec 2009 at 9:25

GoogleCodeExporter commented 8 years ago
Attached is a patch that enables deep stubbing (sounded better than chained 
expectations for this project).  I 
generate the mocks using not-so-kosher access to InvocationContainerImpl, as it 
seems to be my only hope to 
access ArgumentMatches not just the final invocation.

The patch also includes some tests.  I only tested with patterns and such.  
Didn't test it with consecutive 
stubbing (don't expect it to work), or other syntaxes of stub generation (e.g. 
doReturn(...).when(...)).

Original comment by notn...@gmail.com on 5 Dec 2009 at 6:08

Attachments:

GoogleCodeExporter commented 8 years ago
This issue was closed by revision r1816.

Original comment by szcze...@gmail.com on 1 Jan 2010 at 4:37

GoogleCodeExporter commented 8 years ago
In trunk.
Thanks for sharing! I'll do some refactorings later on.

Cheers!
Szczepan

Original comment by szcze...@gmail.com on 1 Jan 2010 at 4:41

GoogleCodeExporter commented 8 years ago

Original comment by szcze...@gmail.com on 28 Feb 2010 at 9:03

GoogleCodeExporter commented 8 years ago
This issue was closed by revision faa7575781.

Original comment by szcze...@gmail.com on 7 Oct 2010 at 5:21

GoogleCodeExporter commented 8 years ago

Original comment by szcze...@gmail.com on 3 Jul 2011 at 12:43