diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java index f67fdfc5a6..46ecb7868a 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/SubclassBytecodeGenerator.java @@ -44,6 +44,7 @@ import net.bytebuddy.implementation.Implementation; import net.bytebuddy.implementation.attribute.MethodAttributeAppender; import net.bytebuddy.matcher.ElementMatcher; +import org.mockito.Answers; import org.mockito.codegen.InjectionBase; import org.mockito.exceptions.base.MockitoException; import org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport.CrossClassLoaderSerializableMock; @@ -240,11 +241,21 @@ public Class mockClass(MockFeatures features) { .serialVersionUid(42L) .defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE) .implement(MockAccess.class) - .intercept(FieldAccessor.ofBeanProperty()) - .method(isHashCode()) - .intercept(hashCode) - .method(isEquals()) - .intercept(equals); + .intercept(FieldAccessor.ofBeanProperty()); + + if (features.defaultAnswer != Answers.CALLS_REAL_METHODS) { + builder = + builder.method(isHashCode()) + .intercept(hashCode) + .method(isEquals()) + .intercept(equals); + } else { + builder = + builder.method(isHashCode()) + .intercept(dispatcher) + .method(isEquals()) + .intercept(dispatcher); + } if (features.serializableMode == SerializableMode.ACROSS_CLASSLOADERS) { builder = builder.implement(CrossClassLoaderSerializableMock.class) diff --git a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java index 30ed949bf5..5ce57b1115 100644 --- a/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java +++ b/src/main/java/org/mockito/internal/creation/bytebuddy/TypeCachingBytecodeGenerator.java @@ -5,12 +5,14 @@ package org.mockito.internal.creation.bytebuddy; import java.lang.ref.ReferenceQueue; +import java.util.Objects; import java.util.Set; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import net.bytebuddy.TypeCache; import org.mockito.mock.SerializableMode; +import org.mockito.stubbing.Answer; class TypeCachingBytecodeGenerator extends ReferenceQueue implements BytecodeGenerator { @@ -43,7 +45,8 @@ public Class mockClass(final MockFeatures params) { params.mockedType, params.interfaces, params.serializableMode, - params.stripAnnotations), + params.stripAnnotations, + params.defaultAnswer), () -> bytecodeGenerator.mockClass(params), BOOTSTRAP_LOCK); } catch (IllegalArgumentException exception) { @@ -83,15 +86,18 @@ private static class MockitoMockKey extends TypeCache.SimpleKey { private final SerializableMode serializableMode; private final boolean stripAnnotations; + private final Answer defaultAnswer; private MockitoMockKey( Class type, Set> additionalType, SerializableMode serializableMode, - boolean stripAnnotations) { + boolean stripAnnotations, + Answer defaultAnswer) { super(type, additionalType); this.serializableMode = serializableMode; this.stripAnnotations = stripAnnotations; + this.defaultAnswer = defaultAnswer; } @Override @@ -107,7 +113,8 @@ public boolean equals(Object object) { } MockitoMockKey that = (MockitoMockKey) object; return stripAnnotations == that.stripAnnotations - && serializableMode.equals(that.serializableMode); + && serializableMode.equals(that.serializableMode) + && Objects.equals(defaultAnswer, that.defaultAnswer); } @Override @@ -115,6 +122,7 @@ public int hashCode() { int result = super.hashCode(); result = 31 * result + (stripAnnotations ? 1 : 0); result = 31 * result + serializableMode.hashCode(); + result = 31 * result + Objects.hashCode(defaultAnswer); return result; } } diff --git a/src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java b/src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java index 30d3784582..a46b276e06 100644 --- a/src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java +++ b/src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java @@ -9,6 +9,7 @@ import static org.junit.Assume.assumeTrue; import static org.mockito.Mockito.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -192,4 +193,13 @@ public void shouldSayNiceMessageWhenSpyingOnPrivateClass() throws Exception { "Most likely it is due to mocking a private class that is not visible to Mockito"); } } + + @Test + public void spysHashCodeEqualsDelegatedToActualMethods() { + List real = new ArrayList<>(); + real.add("one"); + List spy = spy(real); + assertEquals(real.hashCode(), spy.hashCode()); + assertTrue(spy.equals(real)); + } }