| /** |
| * Copyright (C) 2006 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.google.inject; |
| |
| import junit.framework.TestCase; |
| import static com.google.inject.Asserts.assertContains; |
| |
| /** |
| * @author crazybob@google.com (Bob Lee) |
| */ |
| public class CircularDependencyTest extends TestCase { |
| |
| @Override protected void setUp() throws Exception { |
| super.setUp(); |
| Chicken.nextInstanceId = 0; |
| Egg.nextInstanceId = 0; |
| } |
| |
| public void testCircularlyDependentConstructors() |
| throws CreationException { |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| bind(A.class).to(AImpl.class); |
| bind(B.class).to(BImpl.class); |
| } |
| }); |
| |
| A a = injector.getInstance(A.class); |
| assertNotNull(a.getB().getA()); |
| } |
| |
| interface A { |
| B getB(); |
| } |
| |
| @Singleton |
| static class AImpl implements A { |
| final B b; |
| @Inject public AImpl(B b) { |
| this.b = b; |
| } |
| public B getB() { |
| return b; |
| } |
| } |
| |
| interface B { |
| A getA(); |
| } |
| |
| static class BImpl implements B { |
| final A a; |
| @Inject public BImpl(A a) { |
| this.a = a; |
| } |
| public A getA() { |
| return a; |
| } |
| } |
| |
| public void testUnresolvableCircularDependency() { |
| try { |
| Guice.createInjector().getInstance(C.class); |
| fail(); |
| } catch (ProvisionException expected) { |
| assertContains(expected.getMessage(), |
| "Tried proxying " + C.class.getName() + " to support a circular dependency, ", |
| "but it is not an interface."); |
| } |
| } |
| |
| static class C { |
| @Inject C(D d) {} |
| } |
| |
| static class D { |
| @Inject D(C c) {} |
| } |
| |
| /** |
| * As reported by issue 349, we give a lousy trace when a class is circularly |
| * dependent on itself in multiple ways. |
| */ |
| public void testCircularlyDependentMultipleWays() { |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| binder.bind(A.class).to(E.class); |
| binder.bind(B.class).to(E.class); |
| } |
| }); |
| injector.getInstance(A.class); |
| } |
| |
| public void testDisablingCircularProxies() { |
| Injector injector = new InjectorBuilder() |
| .disableCircularProxies() |
| .addModules(new AbstractModule() { |
| protected void configure() { |
| binder.bind(A.class).to(E.class); |
| binder.bind(B.class).to(E.class); |
| } |
| }).build(); |
| |
| try { |
| injector.getInstance(A.class); |
| fail("expected exception"); |
| } catch(ProvisionException expected) { |
| assertContains(expected.getMessage(), |
| "Tried proxying " + A.class.getName() + " to support a circular dependency, but circular proxies are disabled", |
| "Tried proxying " + B.class.getName() + " to support a circular dependency, but circular proxies are disabled"); |
| } |
| } |
| |
| @Singleton |
| static class E implements A, B { |
| @Inject |
| public E(A a, B b) {} |
| |
| public B getB() { |
| return this; |
| } |
| |
| public A getA() { |
| return this; |
| } |
| } |
| |
| static class Chicken { |
| static int nextInstanceId; |
| final int instanceId = nextInstanceId++; |
| @Inject Egg source; |
| } |
| |
| static class Egg { |
| static int nextInstanceId; |
| final int instanceId = nextInstanceId++; |
| @Inject Chicken source; |
| } |
| |
| public void testCircularlyDependentSingletonsWithProviders() { |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| bind(Chicken.class).in(Singleton.class); |
| } |
| |
| @Provides @Singleton Egg provideEgg(Chicken chicken) { |
| Egg egg = new Egg(); |
| egg.source = chicken; |
| return egg; |
| } |
| }); |
| |
| try { |
| injector.getInstance(Egg.class); |
| fail(); |
| } catch (ProvisionException e) { |
| assertContains(e.getMessage(), |
| "Provider was reentrant while creating a singleton", |
| " at " + CircularDependencyTest.class.getName(), "provideEgg(", |
| " while locating " + Egg.class.getName()); |
| } |
| } |
| |
| public void testCircularDependencyProxyDelegateNeverInitialized() { |
| Injector injector = Guice.createInjector(new AbstractModule() { |
| protected void configure() { |
| bind(F.class).to(RealF.class); |
| bind(G.class).to(RealG.class); |
| } |
| }); |
| F f = injector.getInstance(F.class); |
| assertEquals("F", f.g().f().toString()); |
| assertEquals("G", f.g().f().g().toString()); |
| |
| } |
| |
| public interface F { |
| G g(); |
| } |
| |
| @Singleton |
| public static class RealF implements F { |
| private final G g; |
| @Inject RealF(G g) { |
| this.g = g; |
| } |
| |
| public G g() { |
| return g; |
| } |
| |
| @Override public String toString() { |
| return "F"; |
| } |
| } |
| |
| public interface G { |
| F f(); |
| } |
| |
| @Singleton |
| public static class RealG implements G { |
| private final F f; |
| @Inject RealG(F f) { |
| this.f = f; |
| } |
| |
| public F f() { |
| return f; |
| } |
| |
| @Override public String toString() { |
| return "G"; |
| } |
| } |
| |
| } |