Skip to content

Commit

Permalink
Merge branch 'master' of ssh://github.com/Netflix/governator into swi…
Browse files Browse the repository at this point in the history
…tch_to_junit

# Conflicts:
#	governator-test-junit/build.gradle
  • Loading branch information
elandau committed Jun 9, 2016
2 parents 755f495 + 0883f86 commit 0a33894
Show file tree
Hide file tree
Showing 9 changed files with 759 additions and 2 deletions.
9 changes: 7 additions & 2 deletions governator-test-junit/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
apply plugin: 'java'

dependencies {
compile project(':governator')
compile "junit:junit:4.12"
compile project(':governator')
compile project(':governator-api')
compile project(':governator-core')
compile 'com.netflix.archaius:archaius2-guice:2.0.6'
compile 'com.netflix.archaius:archaius2-test:2.0.6'
compile 'junit:junit:4.12'
compile 'org.mockito:mockito-core:1.10.19'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package com.netflix.governator.guice.test;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.inject.Inject;

import org.apache.commons.lang3.ClassUtils;
import org.mockito.Mockito;

import com.google.inject.AbstractModule;
import com.google.inject.Binding;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.binder.AnnotatedBindingBuilder;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.name.Names;
import com.google.inject.spi.DefaultElementVisitor;
import com.google.inject.spi.Element;
import com.google.inject.spi.Elements;
import com.netflix.archaius.api.config.CompositeConfig;
import com.netflix.archaius.api.config.SettableConfig;
import com.netflix.archaius.config.DefaultSettableConfig;
import com.netflix.archaius.guice.Raw;
import com.netflix.archaius.test.TestCompositeConfig;
import com.netflix.archaius.test.TestPropertyOverride;
import com.netflix.archaius.test.TestPropertyOverrideAnnotationReader;
import com.netflix.governator.InjectorBuilder;
import com.netflix.governator.LifecycleInjector;
import com.netflix.governator.providers.SingletonProvider;

public class AnnotationBasedTestInjectorManager {

private final LifecycleInjector injector;
private final List<Object> mocksToReset = new ArrayList<>();
private final List<Module> modulesForTestClass = new ArrayList<>();
private final List<Module> overrideModules = new ArrayList<>();
private final List<Key<?>> spyTargets = new ArrayList<>();
private final SettableConfig classLevelOverrides = new DefaultSettableConfig();
private final SettableConfig methodLevelOverrides = new DefaultSettableConfig();
private final TestPropertyOverrideAnnotationReader testPropertyOverrideAnnotationReader = new TestPropertyOverrideAnnotationReader();
private TestCompositeConfig testCompositeConfig;

public AnnotationBasedTestInjectorManager(Class<?> classUnderTest) {
inspectModulesForTestClass(classUnderTest);
inspectMocksForTestClass(classUnderTest);
inspectSpiesForTargetKeys(Elements.getElements(modulesForTestClass));
testCompositeConfig = new TestCompositeConfig(classLevelOverrides, methodLevelOverrides);
overrideModules.add(new ArchaiusTestConfigOverrideModule(testCompositeConfig));
injector = createInjector(modulesForTestClass, overrideModules);
}

/**
* Injects dependencies into the provided test object.
*/
public void prepareTestFixture(Object testFixture) {
injector.injectMembers(testFixture);
}

public void cleanupMocks() {
for (Object mock : mocksToReset) {
Mockito.reset(mock);
}
}

public void cleanupInjector() {
injector.close();
}

protected LifecycleInjector createInjector(List<Module> modules, List<Module> overrides) {
return InjectorBuilder.fromModules(modules).overrideWith(overrides).createInjector();
}

private void inspectModulesForTestClass(Class<?> testClass) {
final List<Class<? extends Module>> moduleClasses = new ArrayList<>();

moduleClasses.addAll(getModulesForAnnotatedClass(testClass));
for (Class<?> parentClass : getAllSuperClassesInReverseOrder(testClass)) {
moduleClasses.addAll(getModulesForAnnotatedClass(parentClass));
}
for (Class<? extends Module> moduleClass : moduleClasses) {
try {
modulesForTestClass.add(moduleClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
try {
Constructor<?> constructor = moduleClass.getDeclaredConstructors()[0];
constructor.setAccessible(true);
modulesForTestClass.add((Module) constructor.newInstance());
} catch (Exception ex) {
throw new RuntimeException("Error instantiating module " + moduleClass
+ ". Please ensure that the module is public and has a no-arg constructor", e);
}
}
}
}

private List<Class<? extends Module>> getModulesForAnnotatedClass(Class<?> testClass) {
final Annotation annotation = testClass.getAnnotation(ModulesForTesting.class);
if (annotation != null) {
return Arrays.asList(((ModulesForTesting) annotation).value());
} else {
return Collections.emptyList();
}
}

private void inspectMocksForTestClass(Class<?> testClass) {
getMocksForAnnotatedFields(testClass);
for (Class<?> parentClass : getAllSuperClassesInReverseOrder(testClass)) {
getMocksForAnnotatedFields(parentClass);
}
}

private void getMocksForAnnotatedFields(Class<?> testClass) {

for (Field field : testClass.getDeclaredFields()) {
if (field.isAnnotationPresent(ReplaceWithMock.class)) {
overrideModules.add(new MockitoOverrideModule<>(field.getAnnotation(ReplaceWithMock.class), field.getType()));
}
if (field.isAnnotationPresent(WrapWithSpy.class)) {
WrapWithSpy spyAnnotation = field.getAnnotation(WrapWithSpy.class);
if (spyAnnotation.name().isEmpty()) {
spyTargets.add(Key.get(field.getType()));
}
else {
spyTargets.add(Key.get(field.getType(), Names.named(spyAnnotation.name())));
}
}
}
}

private void inspectSpiesForTargetKeys(List<Element> elements) {
for (Element element : elements) {
element.acceptVisitor(new DefaultElementVisitor<Void>() {
@Override
public <T> Void visit(Binding<T> binding) {
final Binding<T> finalBinding = binding;
if (spyTargets.contains(binding.getKey())) {
AbstractModule spyModule = new AbstractModule() {
protected void configure() {
final String finalBindingName = "Spied "
+ (finalBinding.getKey().getAnnotation() != null
? finalBinding.getKey().getAnnotation().toString() : "")
+ finalBinding.getKey().getTypeLiteral();
final Key<T> newUniqueKey = Key.get(finalBinding.getKey().getTypeLiteral(),
Names.named(finalBindingName));
finalBinding.acceptTargetVisitor(new CopyBindingTargetVisitor<>(binder().bind(newUniqueKey)));
bind(finalBinding.getKey()).toProvider(new SingletonProvider<T>() {
@Inject
Injector injector;

protected T create() {
T t = (T) injector.getInstance(newUniqueKey);
return Mockito.spy(t);
};
});
}
};
overrideModules.add(spyModule);
}
return null;
}
});
}
}

public void prepareConfigForTestClass(Class<?> testClass, Method testMethod) {
for(Class<?> parentClass : getAllSuperClassesInReverseOrder(testClass)) {
classLevelOverrides.setProperties(testPropertyOverrideAnnotationReader.getPropertiesForAnnotation(parentClass.getAnnotation(TestPropertyOverride.class)));
}
classLevelOverrides.setProperties(testPropertyOverrideAnnotationReader.getPropertiesForAnnotation(testClass.getAnnotation(TestPropertyOverride.class)));
methodLevelOverrides.setProperties(testPropertyOverrideAnnotationReader.getPropertiesForAnnotation(testMethod.getAnnotation(TestPropertyOverride.class)));
}

public void cleanUpMethodLevelConfig() {
testCompositeConfig.resetForTest();
}
private List<Class<?>> getAllSuperClassesInReverseOrder(Class<?> clazz) {
List<Class<?>> allSuperclasses = ClassUtils.getAllSuperclasses(clazz);
Collections.reverse(allSuperclasses);
return allSuperclasses;
}

private class MockitoOverrideModule<T> extends AbstractModule {
private final ReplaceWithMock annotation;
private final Class<T> classToBind;

public MockitoOverrideModule(ReplaceWithMock annotation, Class<T> classToBind) {
this.annotation = annotation;
this.classToBind = classToBind;
}

@Override
protected void configure() {
final T mock = Mockito.mock(classToBind, annotation.answer().get());
mocksToReset.add(mock);
LinkedBindingBuilder<T> bindingBuilder = bind(classToBind);
if (!annotation.name().isEmpty()) {
bindingBuilder = ((AnnotatedBindingBuilder<T>) bindingBuilder).annotatedWith(Names.named(annotation.name()));
}
bindingBuilder.toInstance(mock);
}
}

private class ArchaiusTestConfigOverrideModule extends AbstractModule {
private TestCompositeConfig config;

public ArchaiusTestConfigOverrideModule(TestCompositeConfig config) {
this.config = config;
}

@Override
protected void configure() {
bind(CompositeConfig.class).annotatedWith(Raw.class).toInstance(config);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.netflix.governator.guice.test;

import java.lang.reflect.Constructor;

import com.google.inject.Scopes;
import com.google.inject.binder.LinkedBindingBuilder;
import com.google.inject.spi.BindingTargetVisitor;
import com.google.inject.spi.ConstructorBinding;
import com.google.inject.spi.ConvertedConstantBinding;
import com.google.inject.spi.ExposedBinding;
import com.google.inject.spi.InstanceBinding;
import com.google.inject.spi.LinkedKeyBinding;
import com.google.inject.spi.ProviderBinding;
import com.google.inject.spi.ProviderInstanceBinding;
import com.google.inject.spi.ProviderKeyBinding;
import com.google.inject.spi.UntargettedBinding;

public class CopyBindingTargetVisitor<T> implements BindingTargetVisitor<T, Void> {

private LinkedBindingBuilder<T> builder;

public CopyBindingTargetVisitor(LinkedBindingBuilder<T> builder) {
this.builder = builder;
}

@Override
public Void visit(InstanceBinding<? extends T> binding) {
builder.toInstance(binding.getInstance());
return null;
}

@SuppressWarnings("deprecation")
@Override
public Void visit(ProviderInstanceBinding<? extends T> binding) {
builder.toProvider(binding.getProviderInstance()).in(Scopes.SINGLETON);
return null;
}

@Override
public Void visit(ProviderKeyBinding<? extends T> binding) {
builder.toProvider(binding.getProviderKey()).in(Scopes.SINGLETON);
return null;
}

@Override
public Void visit(LinkedKeyBinding<? extends T> binding) {
builder.to(binding.getLinkedKey()).in(Scopes.SINGLETON);
return null;
}

@Override
public Void visit(ExposedBinding<? extends T> binding) {
builder.to(binding.getKey()).in(Scopes.SINGLETON);
return null;
}

@Override
public Void visit(UntargettedBinding<? extends T> binding) {
builder.to(binding.getKey().getTypeLiteral()).in(Scopes.SINGLETON);
return null;
}

@SuppressWarnings("unchecked")
@Override
public Void visit(ConstructorBinding<? extends T> binding) {
builder.toConstructor((Constructor<T>) binding.getConstructor().getMember()).in(Scopes.SINGLETON);
return null;
}

@Override
public Void visit(ConvertedConstantBinding<? extends T> binding) {
builder.toInstance(binding.getValue());
return null;
}

@Override
public Void visit(ProviderBinding<? extends T> binding) {
builder.toProvider(binding.getProvider()).in(Scopes.SINGLETON);
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.netflix.governator.guice.test;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import com.google.inject.Module;

/**
* Creates a Governator-Guice Injector using the provided modules for use in testing.
*
* Example:
* <pre>
* {@literal @}RunWith(GovernatorJunit4ClassRunner.class)
* {@literal @}ModulesForTesting({ SomeTestModule.class })
* public class MyTestCase {
*
* {@literal @}Inject
* SomeDependency someDependency;
*
* {@literal @}Test
* public void test() {
* assertNotNull(someDependency);
* }
* }
*
* public class SomeTestModule extends AbstractModule {
*
* {@literal @}Override
* protected void configure() {
*
* bind(SomeDependency.class);
* }
*
* }
* }
* </pre>
*/
@Documented
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface ModulesForTesting {

Class<? extends Module>[] value() default {};

}
Loading

0 comments on commit 0a33894

Please sign in to comment.