package com.github.czyzby.uedi;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Member;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import com.github.czyzby.uedi.error.circular.CircularErrorA;
import com.github.czyzby.uedi.error.circular.CircularErrorB;
import com.github.czyzby.uedi.impl.DefaultContext;
import com.github.czyzby.uedi.scanner.ClassScanner;
import com.github.czyzby.uedi.scanner.nongwt.impl.FixedClassScanner;
import com.github.czyzby.uedi.stereotype.Provider;
import com.github.czyzby.uedi.test.Root;
import com.github.czyzby.uedi.test.TestComponent;
import com.github.czyzby.uedi.test.TestFactory;
import com.github.czyzby.uedi.test.TestProperty;
import com.github.czyzby.uedi.test.TestProvider;
import com.github.czyzby.uedi.test.TestSingleton;
import com.github.czyzby.uedi.test.ambiguous.Ambiguous;
import com.github.czyzby.uedi.test.ambiguous.AmbiguousA;
import com.github.czyzby.uedi.test.ambiguous.AmbiguousB;
import com.github.czyzby.uedi.test.ambiguous.AmbiguousInjector;
import com.github.czyzby.uedi.test.ambiguous.ListDefaultProvider;
import com.github.czyzby.uedi.test.ambiguous.ListFactory;
import com.github.czyzby.uedi.test.ambiguous.NamedAmbiguous;
import com.github.czyzby.uedi.test.classpath.AbstractClassImplementingSingleton;
import com.github.czyzby.uedi.test.classpath.AbstractClassUser;
import com.github.czyzby.uedi.test.custom.CustomFactory;
import com.github.czyzby.uedi.test.custom.CustomFactory.CustomComponent;
import com.github.czyzby.uedi.test.custom.CustomSingleton;
import com.github.czyzby.uedi.test.inject.AbstractWithFields;
import com.github.czyzby.uedi.test.inject.Built;
import com.github.czyzby.uedi.test.inject.CircularA;
import com.github.czyzby.uedi.test.inject.CircularB;
import com.github.czyzby.uedi.test.inject.ConstructorDependency;
import com.github.czyzby.uedi.test.inject.Ignored;
import com.github.czyzby.uedi.test.inject.InjectFactory;
import com.github.czyzby.uedi.test.inject.InjectProperty;
import com.github.czyzby.uedi.test.inject.InjectProvider;
import com.github.czyzby.uedi.test.inject.Injected;
import com.github.czyzby.uedi.test.inject.Injector;
import com.github.czyzby.uedi.test.inject.Provided;
import com.github.czyzby.uedi.test.inject.UsingAbstractWithFields;
import com.github.czyzby.uedi.test.lifecycle.Counter;
import com.github.czyzby.uedi.test.lifecycle.DestroyedA;
import com.github.czyzby.uedi.test.lifecycle.DestroyedB;
import com.github.czyzby.uedi.test.lifecycle.DestroyedC;
import com.github.czyzby.uedi.test.lifecycle.InitiatedA;
import com.github.czyzby.uedi.test.lifecycle.InitiatedB;
import com.github.czyzby.uedi.test.lifecycle.InitiatedC;
/** Should be extended to test every {@link Context} variant with every {@link ClassScanner} implementation.
*
* @author MJ */
public class ContextTest {
private final Context context = new DefaultContext(
new FixedClassScanner(CircularErrorA.class, CircularErrorB.class, Root.class, TestComponent.class,
TestFactory.class, TestProperty.class, TestProvider.class, TestSingleton.class, Ambiguous.class,
AmbiguousA.class, AmbiguousB.class, AmbiguousInjector.class, ListDefaultProvider.class,
ListFactory.class, NamedAmbiguous.class, AbstractClassImplementingSingleton.class,
AbstractClassUser.class, CustomFactory.class, CustomSingleton.class, AbstractWithFields.class,
Built.class, CircularA.class, CircularB.class, ConstructorDependency.class, Ignored.class,
Injected.class, InjectFactory.class, Injector.class, InjectProperty.class, InjectProvider.class,
Provided.class, UsingAbstractWithFields.class, Counter.class, DestroyedA.class, DestroyedB.class,
DestroyedC.class, InitiatedA.class, InitiatedB.class, InitiatedC.class));
@Before
public void scan() {
context.scan(Root.class);
}
@Test
public void shouldFindSingletons() {
assertNotNull(context.get(TestSingleton.class));
assertEquals(context.get(TestSingleton.class), context.get(TestSingleton.class));
}
@Test
public void shouldFindFactories() {
assertNotNull(context.get(TestFactory.class));
assertEquals(context.get(TestFactory.class), context.get(TestFactory.class));
}
@Test
public void shouldFindProviders() {
assertNotNull(context.get(TestProvider.class));
assertEquals(context.get(TestProvider.class), context.get(TestProvider.class));
}
@Test
public void shouldFindProperties() {
assertNotNull(context.get(TestProperty.class));
assertEquals(context.get(TestProperty.class), context.get(TestProperty.class));
assertEquals(TestProperty.VALUE, context.getProperty(TestProperty.ID));
}
@Test
public void shouldFindClassesWithImplementingSuperClass() {
assertNotNull(context.get(AbstractClassUser.class));
}
@Test
public void shouldNotInitiateAbstractClassesAndInterfaces() {
assertEquals(context.get(AbstractClassImplementingSingleton.class), context.get(AbstractClassUser.class));
}
@Test
public void shouldInjectContext() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.context);
assertEquals(context, injector.context);
assertEquals(context.get(Context.class), injector.context);
assertEquals(context.get(Context.class), context.get(Context.class));
}
@Test
public void shouldInjectSingletons() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.injected);
assertEquals(context.get(Injected.class), injector.injected);
assertEquals(context.get(Injected.class), context.get(Injected.class));
}
@Test
public void shouldInjectObjectsBuiltByFactories() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.built);
assertNotEquals(context.get(Built.class), injector.built);
assertNotEquals(context.get(Built.class), context.get(Built.class));
}
@Test
public void shouldAllowPassingConsumersToFactoryMethods() {
final float consumer = 2f;
// InjectFactory contains square method, which consumes a Float parameter and returns a Double. Since this call
// requests a Double instance for (boxed) Float object, consumer should be injected as factory method parameter.
final double result = context.get(Double.class, consumer);
assertEquals(4, result, 0.001);
}
@Test(expected = RuntimeException.class)
public void shouldNotConvertStaticFactoryMethodsToProviders() {
assertNotNull(context.get(InjectFactory.class));
context.get(BigDecimal.class); // Type returned by a static method in InjectFactory.
}
@Test(expected = RuntimeException.class)
public void shouldNotConvertNonPublicFactoryMethodsToProviders() {
assertNotNull(context.get(InjectFactory.class));
context.get(BigInteger.class); // Type returned by non-public methods in InjectFactory.
}
@Test
public void shouldInjectObjectsProvidedByProviders() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.provided);
assertNotEquals(context.get(Provided.class), injector.provided);
assertNotEquals(context.get(Provided.class), context.get(Provided.class));
}
@Test
public void shouldInjectProperties() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.property);
assertEquals(context.getProperty("property"), injector.property);
assertEquals(InjectProperty.VALUE, injector.property);
assertEquals(context.getProperty("property"), context.getProperty("property"));
}
@Test
public void shouldInjectIntoFieldsFromSuperTypes() {
final UsingAbstractWithFields child = context.get(UsingAbstractWithFields.class);
assertNotNull(child.getChildField());
assertNotNull(child.getSuperField());
assertNotNull(((AbstractWithFields) child).getSuperField());
}
@Test
public void shouldNotInjectIntoTransientFields() {
assertNull(context.get(Injector.class).transientValue);
}
@Test
public void shouldNotInjectIntoNotNullFields() {
final Injector injector = context.get(Injector.class);
assertNotNull(injector.notNull);
assertEquals(Ignored.TEST_INSTANCE, injector.notNull);
}
@Test
public void shouldNotInjectIntoStaticFields() {
assertNull(Injector.staticValue);
}
@Test
public void shouldNotInjectIntoPrimitiveFields() {
assertEquals(42, context.get(Injector.class).primitive);
}
@Test
public void shouldResolveCircularFieldInjections() {
assertEquals(context.get(CircularA.class), context.get(CircularB.class).getA());
assertEquals(context.get(CircularB.class), context.get(CircularA.class).getB());
}
@Test
public void shouldResolveConstructorInjections() {
final ConstructorDependency constructor = context.get(ConstructorDependency.class);
assertEquals(context.get(CircularA.class), constructor.getA());
assertEquals(context.get(CircularB.class), constructor.getB());
}
@Test
public void shouldConstructInstanceOnDemand() {
final ConstructorDependency created = context.create(ConstructorDependency.class);
assertEquals(context.get(CircularA.class), created.getA());
assertEquals(context.get(CircularB.class), created.getB());
assertEquals(context.get(Injected.class), created.getInjected());
assertNotEquals(context.get(ConstructorDependency.class), created);
}
@Test
public void shouldInitiateMarkedComponents() {
assertTrue(context.get(InitiatedA.class).wasInitiated());
assertTrue(context.get(InitiatedB.class).wasInitiated());
assertTrue(context.get(InitiatedC.class).wasInitiated());
}
@Test
public void shouldInitiateMarkedComponentsAfterDependenciesAreInjected() {
assertTrue(context.get(InitiatedA.class).wereDependenciesNotNull());
assertTrue(context.get(InitiatedB.class).wereDependenciesNotNull());
assertTrue(context.get(InitiatedC.class).wereDependenciesNotNull());
}
@Test
public void shouldInitiateMarkedComponentsInTheChosenOrder() {
assertTrue(context.get(InitiatedA.class).getActualOrder() < context.get(InitiatedB.class).getActualOrder());
assertTrue(context.get(InitiatedB.class).getActualOrder() < context.get(InitiatedC.class).getActualOrder());
}
@Test
public void shouldInitiateOnDemand() {
final InitiatedA initiatedA = new InitiatedA(context.get(InitiatedB.class));
assertFalse(initiatedA.wasInitiated());
context.initiate(initiatedA);
assertTrue(initiatedA.wasInitiated());
}
@Test
public void shouldInitiateComponentsCreatedOnDemand() {
final InitiatedA initiatedA = context.create(InitiatedA.class);
assertTrue(initiatedA.wasInitiated());
assertNotEquals(context.get(InitiatedA.class), initiatedA);
}
@Test
public void shouldDestroyMarkedComponents() {
assertFalse(context.get(DestroyedA.class).wasDestroyed());
assertFalse(context.get(DestroyedB.class).wasDestroyed());
assertFalse(context.get(DestroyedC.class).wasDestroyed());
context.destroy();
assertTrue(context.get(DestroyedA.class).wasDestroyed());
assertTrue(context.get(DestroyedB.class).wasDestroyed());
assertTrue(context.get(DestroyedC.class).wasDestroyed());
}
@Test
public void shouldDestroyMarkedComponentsAfterDependenciesAreInjected() {
context.destroy();
assertTrue(context.get(DestroyedA.class).wereDependenciesNotNull());
assertTrue(context.get(DestroyedB.class).wereDependenciesNotNull());
assertTrue(context.get(DestroyedC.class).wereDependenciesNotNull());
}
@Test
public void shouldDestroyMarkedComponentsInTheChosenOrder() {
context.destroy();
assertTrue(context.get(DestroyedA.class).getActualOrder() < context.get(DestroyedB.class).getActualOrder());
assertTrue(context.get(DestroyedB.class).getActualOrder() < context.get(DestroyedC.class).getActualOrder());
}
@Test
public void shouldDestroyOnDemand() {
final DestroyedA destroyedA = context.get(DestroyedA.class);
assertFalse(destroyedA.wasDestroyed());
context.destroy(destroyedA);
assertTrue(destroyedA.wasDestroyed());
}
@Test
public void shouldDestroyComponentsCreatedOnDemand() {
final DestroyedA destroyedA = context.create(DestroyedA.class);
assertFalse(destroyedA.wasDestroyed());
context.destroy(destroyedA);
assertTrue(destroyedA.wasDestroyed());
assertNotEquals(context.get(DestroyedA.class), destroyedA);
final DestroyedB destroyedB = new DestroyedB(context.create(InitiatedB.class));
assertFalse(destroyedB.wasDestroyed());
assertNull(destroyedB.getInjectedDependency());
context.initiate(destroyedB);
assertFalse(destroyedB.wasDestroyed());
assertNotNull(destroyedB.getInjectedDependency());
context.destroy();
assertTrue(destroyedB.wasDestroyed());
}
@Test(expected = RuntimeException.class)
public void shouldThrowExceptionWhenAmbiguousDependencyWithNoDefaultIsRequested() {
context.get(Ambiguous.class);
}
@Test
public void shouldReturnValidAmbiguousDependencyWhenGivenCorrectID() {
final Ambiguous ambiguous = context.get("ambiguousA", Ambiguous.class);
assertTrue(ambiguous instanceof AmbiguousA);
assertEquals(context.get(AmbiguousA.class), ambiguous);
}
@Test(expected = RuntimeException.class)
public void shouldThrowExceptionWhenNamedDependencyIsRequestedByLowerCamelCaseClassName() {
context.get("namedAmbiguous", Ambiguous.class);
}
@Test
public void shouldReturnValidAmbiguousDependencyWhenGivenCorrectNameOfNamedComponent() {
final String name = NamedAmbiguous.NAME;
final Ambiguous ambiguous = context.get(name, Ambiguous.class);
assertTrue(ambiguous instanceof NamedAmbiguous);
assertEquals(context.get(NamedAmbiguous.class), ambiguous);
}
@Test
public void shouldInjectCorrectAmbiguousDependenciesWhenFieldNamesMatchComponentIds() {
final AmbiguousInjector injector = context.get(AmbiguousInjector.class);
assertTrue(injector.getA() instanceof AmbiguousA);
assertEquals(context.get(AmbiguousA.class), injector.getA());
assertTrue(injector.getB() instanceof AmbiguousB);
assertEquals(context.get(AmbiguousB.class), injector.getB());
assertTrue(injector.getNamed() instanceof NamedAmbiguous);
assertEquals(context.get(NamedAmbiguous.class), injector.getNamed());
}
@Test
public void shouldResolveAmbiguousDependencyGivenDefaultProvider() {
// ListDefaultProvider implements Default interface, so it should be automatically used when trying to inject a
// value without an ID. It returns array lists.
assertTrue(context.get(AbstractList.class) instanceof ArrayList<?>);
}
@Test
public void shouldTreatFactoryMethodNamesAsProviderIds() {
// ListFactory contains a "linkedList" method which returns instances of linked lists.
assertTrue(context.get("linkedList", List.class) instanceof LinkedList<?>);
}
@Test(expected = RuntimeException.class)
public void shouldRemoveProvidersAfterContextClearing() {
try {
context.get(TestComponent.class);
} catch (final Exception exception) {
fail(exception + " should not be thrown yet.");
}
context.clear();
context.get(TestComponent.class);
}
@Test(expected = RuntimeException.class)
public void shouldRemoveProviderForClass() {
try {
context.get(TestComponent.class);
} catch (final Exception exception) {
fail(exception + " should not be thrown yet.");
}
context.remove(TestComponent.class);
context.get(TestComponent.class);
}
@Test
public void shouldStillProvideContextInstanceEvenAfterClearing() {
context.clear();
assertEquals(context, context.get(Context.class));
}
@Test(expected = RuntimeException.class)
public void shouldClearProvidersForClassTree() {
try {
context.get(AbstractList.class);
} catch (final Exception exception) {
fail(exception + " should not be thrown yet.");
}
context.clear(ArrayList.class);
context.get(AbstractList.class);
}
@Test
public void shouldReplaceProvider() {
final TestComponent previous = context.get(TestComponent.class);
final Provider<TestComponent> newProvider = new Provider<TestComponent>() {
@Override
public Class<? extends TestComponent> getType() {
return TestComponent.class;
}
@Override
public TestComponent provide(final Object target, final Member member) {
return new TestComponent();
}
};
context.replace(TestComponent.class, newProvider);
final TestComponent current = context.get(TestComponent.class);
assertNotEquals(previous, current);
}
@Test
public void shouldAddCustomProperty() {
context.setProperty("custom", "custom value");
assertEquals("custom value", context.getProperty("custom"));
context.setProperty("custom", "new value");
assertEquals("new value", context.getProperty("custom"));
}
@Test
public void shouldAddCustomSingleton() {
assertFalse(context.isAvailable(CustomSingleton.class));
final CustomSingleton custom = new CustomSingleton();
context.add(custom);
assertTrue(context.isAvailable(CustomSingleton.class));
assertEquals(custom, context.get(CustomSingleton.class));
assertEquals(context.get(CustomSingleton.class), context.get(CustomSingleton.class));
}
@Test
public void shouldAddCustomFactory() {
assertFalse(context.isAvailable(CustomFactory.class));
assertFalse(context.isAvailable(CustomComponent.class));
final CustomFactory custom = new CustomFactory();
context.addFactory(custom);
assertTrue(context.isAvailable(CustomFactory.class));
assertTrue(context.isAvailable(CustomComponent.class));
// CustomFactory always returns the same instance of CustomComponent.
assertEquals(custom.getComponent(), context.get(CustomComponent.class));
}
@Test
public void shouldAddCustomProvider() {
assertFalse(context.isAvailable(CustomComponent.class));
context.addProvider(new Provider<CustomComponent>() {
@Override
public Class<? extends CustomComponent> getType() {
return CustomComponent.class;
}
@Override
public CustomComponent provide(final Object target, final Member member) {
return new CustomComponent();
}
});
assertTrue(context.isAvailable(CustomComponent.class));
assertNotEquals(context.get(CustomComponent.class), context.get(CustomComponent.class));
}
@Test(expected = RuntimeException.class)
public void shouldThrowExceptionForUnknownComponent() {
context.get(Assert.class);
}
@Test(expected = RuntimeException.class)
public void shouldThrowExceptionWhenCircularConstructorDependencyIsDetected() {
context.scan(CircularErrorA.class);
}
}