/*
* Grapht, an open source dependency injector.
* Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
* Copyright 2010-2014 Regents of the University of Minnesota
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.grouplens.grapht;
import org.grouplens.grapht.annotation.DefaultImplementation;
import org.grouplens.grapht.annotation.DefaultProvider;
import org.grouplens.grapht.solver.UnresolvableDependencyException;
import org.junit.Test;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class SkipIfUnusableTest {
/**
* A skippable default with satisfied dependencies should be injected.
*/
@Test
public void testSatisfyImplementation() throws InjectionException {
InjectorBuilder bld = InjectorBuilder.create();
bld.bind(Inner.class).to(InnerObj.class);
Injector inj = bld.build();
IfaceWithSkippableDefault obj = inj.getInstance(IfaceWithSkippableDefault.class);
assertThat(obj, instanceOf(DftImpl.class));
}
/**
* A skippable default provider with satisfied dependencies should be injected.
*/
@Test
public void testSatisfyProvider() throws InjectionException {
InjectorBuilder bld = InjectorBuilder.create();
bld.bind(Inner.class).to(InnerObj.class);
Injector inj = bld.build();
IfaceWithSkippableDefaultProvider obj = inj.getInstance(IfaceWithSkippableDefaultProvider.class);
assertThat(obj, instanceOf(ProvidedImpl.class));
}
/**
* A skippable default without satisfied dependencies should not be injected.
*/
@Test
public void testSkipImplementation() {
InjectorBuilder bld = InjectorBuilder.create();
Injector inj = bld.build();
try {
IfaceWithSkippableDefault obj = inj.tryGetInstance(null, IfaceWithSkippableDefault.class);
assertThat(obj, nullValue());
} catch (InjectionException ex) {
fail("injection of skipped object should succeed with null object");
}
}
/**
* A skippable default provider without satisfied dependencies should not be injected.
*/
@Test
public void testSkipProvider() {
InjectorBuilder bld = InjectorBuilder.create();
Injector inj = bld.build();
try {
IfaceWithSkippableDefaultProvider obj = inj.tryGetInstance(null, IfaceWithSkippableDefaultProvider.class);
assertThat(obj, nullValue());
} catch (InjectionException e) {
fail("injection of skipped provider should succeed with null object");
}
}
/**
* A skippable default with a satisfied dependency that itself has an unsatisfied dependency should fail.
*/
@Test
public void testFailWithUnsatisfiedTransitiveDep() {
InjectorBuilder bld = InjectorBuilder.create();
bld.bind(Inner.class).to(InnerWithDep.class);
Injector inj = bld.build();
try {
inj.tryGetInstance(null, IfaceWithSkippableDefault.class);
fail("injecting a skippable default with a satisfied but instantiable dep should fail");
} catch (InjectionException ex) {
assertThat(ex, instanceOf(UnresolvableDependencyException.class));
}
}
/**
* Injection of component that depends on a skipped default should fail.
*/
@Test
public void testFailWithUnusableDefaultForDep() {
InjectorBuilder bld = InjectorBuilder.create();
Injector inj = bld.build();
try {
inj.getInstance(null, DefaultRequirer.class);
fail("injection of dep on skipped default should fail");
} catch (InjectionException ex) {
assertThat(ex, instanceOf(UnresolvableDependencyException.class));
}
}
/**
* Injection of component that optionally uses a skipped default should succeed.
*/
@Test
public void testAcceptSkippedOptionalDep() {
InjectorBuilder bld = InjectorBuilder.create();
Injector inj = bld.build();
try {
OptionalDefaultRequirer obj = inj.getInstance(OptionalDefaultRequirer.class);
assertThat(obj, notNullValue());
} catch (InjectionException ex) {
fail("injection of component with optional dep on skipped default should succeed");
}
}
/**
* Injection of component that optionally uses a non-skipped but transitively non-instantiable default should fail.
*/
@Test
public void testFailOptionalDepHasUnmetDep() {
InjectorBuilder bld = InjectorBuilder.create();
bld.bind(Inner.class).to(InnerWithDep.class);
Injector inj = bld.build();
try {
OptionalDefaultRequirer obj = inj.getInstance(OptionalDefaultRequirer.class);
fail("injection of component with optional dep on non-skipped but uninstantiable default should fail");
} catch (InjectionException ex) {
assertThat(ex, instanceOf(UnresolvableDependencyException.class));
}
}
/**
* Skippable defaults dependend on by skippable defaults should cleanly be skipped.
*/
@Test
public void testNestedSkipping() throws InjectionException {
InjectorBuilder bld = InjectorBuilder.create();
Injector inj = bld.build();
Outer obj = inj.tryGetInstance(null, Outer.class);
assertThat(obj, nullValue());
}
/**
* Interface for dependencies.
*/
interface Inner {
}
/**
* Implementation of dependency interface.
*/
static class InnerObj implements Inner {
@Inject
public InnerObj() {}
}
/**
* Implementation of dependency with a dependency.
*/
static class InnerWithDep implements Inner {
private final String message;
@Inject
public InnerWithDep(String msg) {
message = msg;
}
public String getMessage() {
return message;
}
}
/**
* Interface with a default that is skippable.
*/
@DefaultImplementation(value=DftImpl.class, skipIfUnusable = true)
interface IfaceWithSkippableDefault {
Inner getInner();
}
/**
* Default implementation of the skippable interface.
*/
static class DftImpl implements IfaceWithSkippableDefault {
private final Inner inner;
@Inject
public DftImpl(Inner in) {
inner = in;
}
@Override
public Inner getInner() {
return inner;
}
}
/**
* Interface with a default provider that is skippable.
*/
@DefaultProvider(value=DftProvider.class, skipIfUnusable = true)
interface IfaceWithSkippableDefaultProvider {
Inner getInner();
}
/**
* Default provider for the skippable interface.
*/
static class DftProvider implements Provider<IfaceWithSkippableDefaultProvider> {
private final Inner inner;
@Inject
public DftProvider(Inner in) {
inner = in;
}
@Override
public IfaceWithSkippableDefaultProvider get() {
return new ProvidedImpl(inner);
}
}
static class ProvidedImpl implements IfaceWithSkippableDefaultProvider {
private final Inner inner;
public ProvidedImpl(Inner in) {
inner = in;
}
@Override
public Inner getInner() {
return inner;
}
}
@DefaultImplementation(value = DefaultRequirer.class, skipIfUnusable = true)
interface Outer {}
/**
* A component that requires an object with one of our skippable defaults.
*/
static class DefaultRequirer implements Outer {
private final IfaceWithSkippableDefault dependency;
@Inject
public DefaultRequirer(IfaceWithSkippableDefault dep) {
dependency = dep;
}
public IfaceWithSkippableDefault getDependency() {
return dependency;
}
}
/**
* A component that optionally uses an object with one of our skippable defaults.
*/
static class OptionalDefaultRequirer {
private final IfaceWithSkippableDefault dependency;
@Inject
public OptionalDefaultRequirer(@Nullable IfaceWithSkippableDefault dep) {
dependency = dep;
}
public IfaceWithSkippableDefault getDependency() {
return dependency;
}
}
}