Skip to content

Commit

Permalink
Call real hashCode/equals methods for spies (#2369)
Browse files Browse the repository at this point in the history
Fixes #2331

Co-authored-by: saurabh7248 <thesoundandthefury@protonmail.com>
  • Loading branch information
saurabh7248 and saurabh7248 committed Jul 30, 2021
1 parent 2c5dc2b commit 123beb8
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 15 deletions.
Expand Up @@ -393,7 +393,8 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
settings.getTypeToMock(),
settings.getExtraInterfaces(),
settings.getSerializableMode(),
settings.isStripAnnotations()));
settings.isStripAnnotations(),
settings.getDefaultAnswer()));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
}
Expand Down
Expand Up @@ -8,30 +8,36 @@
import java.util.Set;

import org.mockito.mock.SerializableMode;
import org.mockito.stubbing.Answer;

class MockFeatures<T> {

final Class<T> mockedType;
final Set<Class<?>> interfaces;
final SerializableMode serializableMode;
final boolean stripAnnotations;
final Answer defaultAnswer;

private MockFeatures(
Class<T> mockedType,
Set<Class<?>> interfaces,
SerializableMode serializableMode,
boolean stripAnnotations) {
boolean stripAnnotations,
Answer defaultAnswer) {
this.mockedType = mockedType;
this.interfaces = Collections.unmodifiableSet(interfaces);
this.serializableMode = serializableMode;
this.stripAnnotations = stripAnnotations;
this.defaultAnswer = defaultAnswer;
}

public static <T> MockFeatures<T> withMockFeatures(
Class<T> mockedType,
Set<Class<?>> interfaces,
SerializableMode serializableMode,
boolean stripAnnotations) {
return new MockFeatures<T>(mockedType, interfaces, serializableMode, stripAnnotations);
boolean stripAnnotations,
Answer defaultAnswer) {
return new MockFeatures<T>(
mockedType, interfaces, serializableMode, stripAnnotations, defaultAnswer);
}
}
Expand Up @@ -79,7 +79,8 @@ public <T> Class<? extends T> createMockType(MockCreationSettings<T> settings) {
settings.getTypeToMock(),
settings.getExtraInterfaces(),
settings.getSerializableMode(),
settings.isStripAnnotations()));
settings.isStripAnnotations(),
settings.getDefaultAnswer()));
} catch (Exception bytecodeGenerationFailed) {
throw prettifyFailure(settings, bytecodeGenerationFailed);
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -237,14 +238,24 @@ public <T> Class<? extends T> mockClass(MockFeatures<T> features) {
features.stripAnnotations
? MethodAttributeAppender.NoOp.INSTANCE
: INCLUDING_RECEIVER)
.method(isHashCode())
.intercept(hashCode)
.method(isEquals())
.intercept(equals)
.serialVersionUid(42L)
.defineField("mockitoInterceptor", MockMethodInterceptor.class, PRIVATE)
.implement(MockAccess.class)
.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)
Expand Down
Expand Up @@ -77,7 +77,7 @@ public String toString() {

@Override
public boolean matches(Invocation candidate) {
return invocation.getMock().equals(candidate.getMock())
return invocation.getMock() == candidate.getMock()
&& hasSameMethod(candidate)
&& argumentsMatch(candidate);
}
Expand Down
Expand Up @@ -18,6 +18,7 @@

import org.junit.Before;
import org.junit.Test;
import org.mockito.Answers;
import org.mockito.mock.SerializableMode;
import org.mockitoutil.VmArgAssumptions;

Expand Down Expand Up @@ -46,7 +47,8 @@ public void ensure_cache_is_cleared_if_no_reference_to_classloader_and_classes()
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
Reference<Object> typeReference =
Expand Down Expand Up @@ -79,15 +81,17 @@ public void ensure_cache_returns_same_instance() throws Exception {
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

Class<?> other_mock_type =
cachingMockBytecodeGenerator.mockClass(
withMockFeatures(
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

assertThat(other_mock_type).isSameAs(the_mock_type);

Expand Down Expand Up @@ -123,15 +127,17 @@ public void ensure_cache_returns_different_instance_serializableMode() throws Ex
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.NONE,
false));
false,
Answers.RETURNS_DEFAULTS));

Class<?> other_mock_type =
cachingMockBytecodeGenerator.mockClass(
withMockFeatures(
classloader_with_life_shorter_than_cache.loadClass("foo.Bar"),
Collections.<Class<?>>emptySet(),
SerializableMode.BASIC,
false));
false,
Answers.RETURNS_DEFAULTS));

assertThat(other_mock_type).isNotSameAs(the_mock_type);
}
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/org/mockitousage/spies/SpyingOnRealObjectsTest.java
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> real = new ArrayList<>();
real.add("one");
List<String> spy = spy(real);
assertEquals(real.hashCode(), spy.hashCode());
assertTrue(spy.equals(real));
}
}

0 comments on commit 123beb8

Please sign in to comment.