-
Notifications
You must be signed in to change notification settings - Fork 180
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of ssh://github.com/Netflix/governator into swi…
…tch_to_junit # Conflicts: # governator-test-junit/build.gradle
- Loading branch information
Showing
9 changed files
with
759 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | ||
} |
223 changes: 223 additions & 0 deletions
223
...t/src/main/java/com/netflix/governator/guice/test/AnnotationBasedTestInjectorManager.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
81 changes: 81 additions & 0 deletions
81
...-test-junit/src/main/java/com/netflix/governator/guice/test/CopyBindingTargetVisitor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
governator-test-junit/src/main/java/com/netflix/governator/guice/test/ModulesForTesting.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 {}; | ||
|
||
} |
Oops, something went wrong.