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 4943565
Show file tree
Hide file tree
Showing 12 changed files with 64 additions and 54 deletions.
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
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
@@ -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
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
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
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
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
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
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
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);
}
}
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
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 4943565

Please sign in to comment.