Skip to content

Commit

Permalink
Rework spying on concrete instances
Browse files Browse the repository at this point in the history
  • Loading branch information
leonard84 committed Dec 31, 2017
1 parent c60373c commit 635401d
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ public interface IMockConfiguration {
Class<?> getType();

/**
* Returns the instance to be used as a delegate
* Returns the instance to be used as a prototype
*
* @return the instance to be used as a delegate
* @return the instance to be used as a prototype
*/
@Nullable
Object getInstance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import org.spockframework.mock.runtime.SpecificationAttachable;
import org.spockframework.util.Nullable;

import spock.lang.Specification;

import java.lang.reflect.Type;
Expand Down Expand Up @@ -51,13 +50,6 @@ public interface IMockObject extends SpecificationAttachable {
* @return the instance of this mock object
*/
Object getInstance();

/**
* Returns the original instance provided by the user for wrapping with a Spy, or {@code null} if not applicable.
*
* @return the the original instance provided by the user for wrapping with a Spy, or {@code null} if not applicable.
*/
Object getUserCreatedInstance();

/**
* Tells whether this mock object supports verification of invocations.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.spockframework.mock.runtime;

import org.spockframework.mock.IMockInvocation;
import org.spockframework.mock.IMockMethod;
import org.spockframework.mock.IResponseGenerator;
import org.spockframework.mock.*;
import org.spockframework.util.ExceptionUtil;
import org.spockframework.util.ReflectionUtil;

public class ByteBuddyMethodInvoker implements IResponseGenerator {

Expand All @@ -20,12 +17,6 @@ public Object respond(IMockInvocation invocation) {
throw new IllegalStateException("Cannot invoke abstract method " + invocation.getMethod());
}
try {
Object userCreatedInstance = invocation.getMockObject().getUserCreatedInstance();
if(userCreatedInstance != null) {
IMockMethod method = invocation.getMethod();
Class<?>[] parameterTypes = method.getParameterTypes().toArray(new Class<?>[method.getParameterTypes().size()]);
return ReflectionUtil.getMethodBySignature(userCreatedInstance.getClass(), method.getName(), parameterTypes).invoke(userCreatedInstance, invocation.getArguments().toArray());
}
return superCall.call(invocation.getArguments().toArray());
} catch (Throwable t) {
// Byte Buddy doesn't wrap exceptions in InvocationTargetException, so no need to unwrap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,7 @@ public CglibRealMethodInvoker(MethodProxy methodProxy) {
@Override
public Object respond(IMockInvocation invocation) {
try {
if(invocation.getMockObject().getUserCreatedInstance() != null) {
return methodProxy.invoke(invocation.getMockObject().getUserCreatedInstance(), invocation.getArguments().toArray());
} else {
return methodProxy.invokeSuper(invocation.getMockObject().getInstance(), invocation.getArguments().toArray());
}
return methodProxy.invokeSuper(invocation.getMockObject().getInstance(), invocation.getArguments().toArray());
} catch (Throwable t) {
// MethodProxy doesn't wrap exceptions in InvocationTargetException, so no need to unwrap
ExceptionUtil.sneakyThrow(t);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.*;

public class CompositeMockFactory implements IMockFactory {
public static CompositeMockFactory INSTANCE =
public static final CompositeMockFactory INSTANCE =
new CompositeMockFactory(Arrays.asList(JavaMockFactory.INSTANCE, GroovyMockFactory.INSTANCE));

private final List<IMockFactory> mockFactories;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@

import org.spockframework.mock.*;
import org.spockframework.runtime.GroovyRuntimeUtil;
import org.spockframework.util.ReflectionUtil;
import spock.lang.Specification;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.*;

import groovy.lang.*;

public class GroovyMockFactory implements IMockFactory {
public static GroovyMockFactory INSTANCE = new GroovyMockFactory();
public static final GroovyMockFactory INSTANCE = new GroovyMockFactory();

@Override
public boolean canCreate(IMockConfiguration configuration) {
Expand Down Expand Up @@ -69,11 +70,15 @@ public void run() {
}

IProxyBasedMockInterceptor mockInterceptor = new GroovyMockInterceptor(configuration, specification, newMetaClass);
ArrayList<Class<?>> additionalInterfaces = new ArrayList<>(configuration.getAdditionalInterfaces());
List<Class<?>> additionalInterfaces = new ArrayList<>(configuration.getAdditionalInterfaces());
additionalInterfaces.add(GroovyObject.class);
return ProxyBasedMockFactory.INSTANCE.create(type, additionalInterfaces,
configuration.getConstructorArgs(), mockInterceptor, specification.getClass().getClassLoader(),
configuration.isUseObjenesis());
Object proxy = ProxyBasedMockFactory.INSTANCE.create(type, additionalInterfaces,
configuration.getConstructorArgs(), mockInterceptor, specification.getClass().getClassLoader(),
configuration.isUseObjenesis());
if ((configuration.getNature() == MockNature.SPY) && (configuration.getInstance() != null)) {
ReflectionUtil.deepCopyFields(configuration.getInstance(), proxy);
}
return proxy;
}

private boolean isFinalClass(Class<?> type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@

import org.spockframework.mock.*;
import org.spockframework.runtime.GroovyRuntimeUtil;
import org.spockframework.util.ReflectionUtil;
import spock.lang.Specification;

import java.lang.reflect.Modifier;

import groovy.lang.MetaClass;

public class JavaMockFactory implements IMockFactory {
public static JavaMockFactory INSTANCE = new JavaMockFactory();
public static final JavaMockFactory INSTANCE = new JavaMockFactory();

@Override
public boolean canCreate(IMockConfiguration configuration) {
Expand Down Expand Up @@ -54,9 +55,13 @@ private Object createInternal(IMockConfiguration configuration, Specification sp

MetaClass mockMetaClass = GroovyRuntimeUtil.getMetaClass(configuration.getType());
IProxyBasedMockInterceptor interceptor = new JavaMockInterceptor(configuration, specification, mockMetaClass);
return ProxyBasedMockFactory.INSTANCE.create(configuration.getType(), configuration.getAdditionalInterfaces(),
configuration.getConstructorArgs(), interceptor, classLoader,
configuration.isUseObjenesis());
Object proxy = ProxyBasedMockFactory.INSTANCE.create(configuration.getType(), configuration.getAdditionalInterfaces(),
configuration.getConstructorArgs(), interceptor, classLoader,
configuration.isUseObjenesis());
if ((configuration.getNature() == MockNature.SPY) && (configuration.getInstance() != null)) {
ReflectionUtil.deepCopyFields(configuration.getInstance(), proxy);
}
return proxy;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public JavaMockInterceptor(IMockConfiguration mockConfiguration, Specification s
@Override
public Object intercept(Object target, Method method, Object[] arguments, IResponseGenerator realMethodInvoker) {
IMockObject mockObject = new MockObject(mockConfiguration.getName(), mockConfiguration.getExactType(),
target, mockConfiguration.getInstance(), mockConfiguration.isVerified(), false, mockConfiguration.getDefaultResponse(), specification, this);
target, mockConfiguration.isVerified(), false, mockConfiguration.getDefaultResponse(), specification, this);

if (method.getDeclaringClass() == ISpockMockObject.class) {
return mockObject;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public class MockObject implements IMockObject {
private final String name;
private final Type type;
private final Object instance;
private final Object userCreatedInstance;
private final boolean verified;
private final boolean global;
private final IDefaultResponse defaultResponse;
Expand All @@ -37,15 +36,9 @@ public class MockObject implements IMockObject {

public MockObject(@Nullable String name, Type type, Object instance, boolean verified, boolean global,
IDefaultResponse defaultResponse, Specification specification, SpecificationAttachable mockInterceptor) {
this(name, type, instance, null, verified, global, defaultResponse, specification, mockInterceptor);
}

public MockObject(@Nullable String name, Type type, Object instance, Object userCreatedInstance, boolean verified, boolean global,
IDefaultResponse defaultResponse, Specification specification, SpecificationAttachable mockInterceptor) {
this.name = name;
this.type = type;
this.instance = instance;
this.userCreatedInstance = userCreatedInstance;
this.verified = verified;
this.global = global;
this.defaultResponse = defaultResponse;
Expand Down Expand Up @@ -74,11 +67,6 @@ public Object getInstance() {
return instance;
}

@Override
public Object getUserCreatedInstance() {
return userCreatedInstance;
}

@Override
public boolean isVerified() {
return verified;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,4 +242,29 @@ public static boolean isToStringOverridden(Class<?> valueClass) {
return false;
}
}

public static void deepCopyFields(Object source, Object target) {
if (!source.getClass().isAssignableFrom(target.getClass())) {
throw new IllegalArgumentException("source and target are not compatible.");
}
Class<?> clazz = source.getClass();
while (!clazz.equals(Object.class)) {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
copyField(field, source, target);
}
clazz = clazz.getSuperclass();
}
}

private static void copyField(Field field, Object source, Object target) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
field.set(target, field.get(source));
} catch (IllegalAccessException e) {
// ignore
}
field.setAccessible(accessible);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ class JavaSpies extends Specification {
result == "singing, singing"
}
@Issue("https://github.com/spockframework/spock/issues/771")
def "sping on concrete instances can use partial mocking"() {
def person = Spy(new Person())
when:
def result = person.work()
then:
1 * person.work()
1 * person.getTask() >> "work"
result == "work, work"
}
@Issue("https://github.com/spockframework/spock/issues/769")
def "can spy on instances of classes with no default constructor"() {
given:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,6 @@ public Object getInstance() {
return null;
}

@Override
public Object getUserCreatedInstance() {
return null;
}

@Override
public boolean isVerified() {
return false;
Expand Down

0 comments on commit 635401d

Please sign in to comment.