package ru.vyarus.dropwizard.guice.test.spock.ext;
import com.google.inject.Injector;
import com.google.inject.spi.InjectionPoint;
import org.junit.rules.ExternalResource;
import org.spockframework.runtime.extension.AbstractMethodInterceptor;
import org.spockframework.runtime.extension.IMethodInvocation;
import org.spockframework.runtime.model.SpecInfo;
import spock.lang.Shared;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;
/**
* Leverages rules logic to start/stop application and injects Guice-provided objects into specifications.
* <p>Implementation is very similar to original spock-guice module.</p>
*
* @author Vyacheslav Rusakov
* @since 02.01.2015
*/
// Important implementation detail: Only the fixture methods of
// spec.getTopSpec() are intercepted (see GuiceExtension)
public class GuiceyInterceptor extends AbstractMethodInterceptor {
private static Method before;
private static Method after;
private final ExternalRuleAdapter externalRuleAdapter;
private final Set<InjectionPoint> injectionPoints;
private ExternalResource resource;
static {
// resolve methods eagerly to speedup execution
try {
before = ExternalResource.class.getDeclaredMethod("before");
before.setAccessible(true);
after = ExternalResource.class.getDeclaredMethod("after");
after.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new GuiceyExtensionException("Failed resolve method", e);
}
}
public GuiceyInterceptor(final SpecInfo spec, final ExternalRuleAdapter externalRuleAdapter) {
this.externalRuleAdapter = externalRuleAdapter;
injectionPoints = InjectionPoint.forInstanceMethodsAndFields(spec.getReflection());
}
@Override
public void interceptSharedInitializerMethod(final IMethodInvocation invocation) throws Throwable {
if (resource == null) {
resource = externalRuleAdapter.newResource();
}
before.invoke(resource);
injectValues(invocation.getSharedInstance(), true);
invocation.proceed();
}
@Override
public void interceptInitializerMethod(final IMethodInvocation invocation) throws Throwable {
injectValues(invocation.getInstance(), false);
invocation.proceed();
}
@Override
public void interceptCleanupSpecMethod(final IMethodInvocation invocation) throws Throwable {
try {
invocation.proceed();
} finally {
after.invoke(resource);
}
}
private void injectValues(final Object target, final boolean sharedFields) throws IllegalAccessException {
for (InjectionPoint point : injectionPoints) {
if (!(point.getMember() instanceof Field)) {
throw new GuiceyExtensionException("Method injection is not supported; use field injection instead");
}
final Field field = (Field) point.getMember();
if (field.isAnnotationPresent(Shared.class) != sharedFields) {
continue;
}
final Object value = externalRuleAdapter.getInjector().getInstance(point.getDependencies().get(0).getKey());
field.setAccessible(true);
field.set(target, value);
}
}
/**
* External junit rules adapter.
*/
public interface ExternalRuleAdapter {
/**
* @return new rule instance
*/
ExternalResource newResource();
/**
* @return injector instance
*/
Injector getInjector();
}
}