Weltraumschaf
Weltraumschaf

Reputation: 428

Mockito produces StackOverflowError when mocked method is called in equals()/hashCode()

This example code:

public final class FooBarTest {
    @Test
    public void test() {
        final Foo foo = mock(Foo.class);
        when(foo.getBar()).thenReturn(1);
        new HashSet().add(foo);
    }

    private class Foo {
        @Override
        public final boolean equals(final Object other) {
            return getBar() == 0;
        }

        public int getBar() {
            return 0;
        }

        @Override
        public final int hashCode() {
            return getBar();
        }
    }
}

produces an endless loop and throws an exception:

java.lang.StackOverflowError
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:57)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:43)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:119)
    at de.weltraumschaf.maconha.FooBarTest$Foo$MockitoMock$217383798.getBar(Unknown Source)
    at de.weltraumschaf.maconha.FooBarTest$Foo.equals(FooBarTest.java:24)
    at org.mockito.internal.invocation.InvocationMatcher.matches(InvocationMatcher.java:81)
    at org.mockito.internal.stubbing.InvocationContainerImpl.findAnswerFor(InvocationContainerImpl.java:82)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:90)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:32)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:36)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:57)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:43)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:119)
    at de.weltraumschaf.maconha.FooBarTest$Foo$MockitoMock$217383798.getBar(Unknown Source)
    at de.weltraumschaf.maconha.FooBarTest$Foo.equals(FooBarTest.java:24)
    at org.mockito.internal.invocation.InvocationMatcher.matches(InvocationMatcher.java:81)
    at org.mockito.internal.stubbing.InvocationContainerImpl.findAnswerFor(InvocationContainerImpl.java:82)
    at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:90)
    at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:32)
    at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:36)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:57)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:43)
    at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptSuperCallable(MockMethodInterceptor.java:119)
    at de.weltraumschaf.maconha.FooBarTest$Foo$MockitoMock$217383798.getBar(Unknown Source)
    at de.weltraumschaf.maconha.FooBarTest$Foo.equals(FooBarTest.java:24)
    ...

I debugged into the code and got lost in the depth of Mockito. The only thing I can see is that the equals method is called right. I know that Mockito does not stub equals/hashCode/toString. It is also clear to me that the equals method is called by the HashSet to see if there is already an equal element in it. But what I can't figure out: Why does this loop endless?

The used Mockito version is 2.8.9 and JUnit 4.12.

Upvotes: 0

Views: 4588

Answers (1)

luk2302
luk2302

Reputation: 57124

Because in the callstack there is org.mockito.internal.invocation.InvocationMatcher#matches which basically is
invocation.getMock().equals(candidate.getMock()) (with invocation.getMock() being your mocked Foo instance) which causes a call to getBar which is mocked which causes a call to equals -> getBar -> mock -> equals -> getBar -> etc...

Upvotes: 4

Related Questions