/* * Copyright (C) 2011 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.gwt.inject.rebind.resolution; import static com.google.gwt.inject.rebind.resolution.TestUtils.asyncProviderFoo; import static com.google.gwt.inject.rebind.resolution.TestUtils.bar; import static com.google.gwt.inject.rebind.resolution.TestUtils.baz; import static com.google.gwt.inject.rebind.resolution.TestUtils.foo; import static com.google.gwt.inject.rebind.resolution.TestUtils.fooImpl; import static com.google.gwt.inject.rebind.resolution.TestUtils.providerBar; import static com.google.gwt.inject.rebind.resolution.TestUtils.providerBaz; import static com.google.gwt.inject.rebind.resolution.TestUtils.providerFoo; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.eq; import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.isA; import static org.easymock.EasyMock.createControl; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.inject.rebind.ErrorManager; import com.google.gwt.inject.rebind.GinjectorBindings; import com.google.gwt.inject.rebind.binding.Binding; import com.google.gwt.inject.rebind.binding.BindingFactory; import com.google.gwt.inject.rebind.binding.Context; import com.google.gwt.inject.rebind.binding.Dependency; import com.google.gwt.inject.rebind.binding.ExposedChildBinding; import com.google.gwt.inject.rebind.binding.ParentBinding; import com.google.gwt.inject.rebind.resolution.ImplicitBindingCreator.BindingCreationException; import com.google.inject.Key; import junit.framework.TestCase; import org.easymock.Capture; import org.easymock.EasyMock; import org.easymock.IMocksControl; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; public class BindingResolverTest extends TestCase { private static final String SOURCE = "source"; // List to record binding collections that we mock so that they don't get GC'd and have // false failures due to finalize being called private List<GinjectorBindings> nodes = new ArrayList<GinjectorBindings>(); private List<Binding> bindings = new ArrayList<Binding>(); private ImplicitBindingCreator bindingCreator; private ErrorManager errorManager; private ParentBinding parentBinding; private IMocksControl control; private BindingResolver bindingResolver; private TreeLogger treeLogger; private BindingFactory bindingFactory; private void replay() { control.replay(); } private void verify() { EasyMock.verify(treeLogger); control.verify(); } protected void setUp() throws Exception { super.setUp(); treeLogger = EasyMock.createNiceMock("treeLogger", TreeLogger.class); // Ensure that branches get the same mock logger and not a null pointer. expect(treeLogger.branch(EasyMock.<TreeLogger.Type>anyObject(), EasyMock.<String>anyObject(), EasyMock.<Throwable>anyObject(), EasyMock.<TreeLogger.HelpInfo>anyObject())) .andReturn(treeLogger) .anyTimes(); EasyMock.replay(treeLogger); control = createControl(); parentBinding = control.createMock("parentBinding", ParentBinding.class); bindingCreator = control.createMock("bindingCreator", ImplicitBindingCreator.class); errorManager = control.createMock("errorManager", ErrorManager.class); bindingFactory = control.createMock("bindingFactory", BindingFactory.class); final ImplicitBindingCreator.Factory bindingCreatorFactory = new ImplicitBindingCreator.Factory() { @Override public ImplicitBindingCreator create(TreeLogger logger) { return bindingCreator; } }; DependencyExplorer.Factory dependencyExplorerFactory = new DependencyExplorer.Factory() { @Override public DependencyExplorer create(TreeLogger logger) { return new DependencyExplorer(bindingCreatorFactory, logger); } }; UnresolvedBindingValidator.Factory unresolvedBindingValidatorFactory = new UnresolvedBindingValidator.Factory() { @Override public UnresolvedBindingValidator create(TreeLogger logger) { return new UnresolvedBindingValidator(new EagerCycleFinder(errorManager), errorManager, logger); } }; final BindingPositioner.Factory bindingPositionerFactory = new BindingPositioner.Factory() { @Override public BindingPositioner create(TreeLogger logger) { return new BindingPositioner(logger); } }; BindingInstaller.Factory bindingInstallerFactory = new BindingInstaller.Factory() { @Override public BindingInstaller create(TreeLogger logger) { return new BindingInstaller(bindingPositionerFactory, bindingFactory, logger); } }; bindingResolver = new BindingResolver( dependencyExplorerFactory, unresolvedBindingValidatorFactory, bindingInstallerFactory, treeLogger); } private GinjectorBindings createInjectorNode(String name) { GinjectorBindings node = control.createMock(name, GinjectorBindings.class); nodes.add(node); expect(node.getParent()).andStubReturn(null); expect(node.getChildren()).andStubReturn(new ArrayList<GinjectorBindings>()); expect(node.isBound(isA(Key.class))).andStubReturn(false); expect(node.isBoundLocallyInChild(isA(Key.class))).andStubReturn(false); expect(node.getBinding(isA(Key.class))).andStubReturn(null); expect(node.getModuleName()).andStubReturn(name); expect(node.isPinned(isA(Key.class))).andStubReturn(false); return node; } // TODO(bchambers): This may be cleaner if we extract an interface for BindingResolver // to use when resolving bindings, and then create a "test" version of that. private void setChildren(GinjectorBindings parent, GinjectorBindings... children) { expect(parent.getChildren()).andReturn(Arrays.asList(children)).anyTimes(); for (GinjectorBindings child : children) { expect(child.getParent()).andReturn(parent).anyTimes(); } } // Creates the following hierarchy of binding collections and returns a // StandardTree object with names for all the nodes. // root // / \ // childL childR // / \ // childLL childLR public StandardTree createExampleTree() { StandardTree tree = new StandardTree(); tree.root = createInjectorNode("root"); tree.childL = createInjectorNode("childL"); tree.childR = createInjectorNode("childR"); tree.childLL = createInjectorNode("childLL"); tree.childLR = createInjectorNode("childLR"); setChildren(tree.root, tree.childL, tree.childR); setChildren(tree.childL, tree.childLL, tree.childLR); return tree; } private static class StandardTree { private GinjectorBindings root; private GinjectorBindings childL; private GinjectorBindings childLL; private GinjectorBindings childLR; private GinjectorBindings childR; } private void bind(Key<?> key, GinjectorBindings in) { Binding binding = control.createMock(Binding.class); expect(binding.getContext()).andReturn(Context.forText("")).anyTimes(); expect(in.isBound(key)).andReturn(true).anyTimes(); expect(in.getBinding(key)).andReturn(binding).anyTimes(); bindings.add(binding); } private void bindChild(Key<?> key, GinjectorBindings parent, GinjectorBindings child) { ExposedChildBinding binding = control.createMock(ExposedChildBinding.class); expect(binding.getContext()).andReturn(Context.forText("")).anyTimes(); expect(parent.isBound(key)).andReturn(true).anyTimes(); expect(parent.getBinding(key)).andReturn(binding).anyTimes(); expect(child.isPinned(key)).andReturn(true).anyTimes(); expect(binding.getChildBindings()).andReturn(child).anyTimes(); bindings.add(binding); } private void bindParent(Key<?> key, GinjectorBindings parent, GinjectorBindings child) { ParentBinding binding = control.createMock(ParentBinding.class); expect(binding.getContext()).andReturn(Context.forText("")).anyTimes(); expect(binding.getParentBindings()).andReturn(parent).anyTimes(); expect(child.isBound(key)).andReturn(true).anyTimes(); expect(child.getBinding(key)).andReturn(binding).anyTimes(); bindings.add(binding); } private void expectParentBinding(Key<?> key, GinjectorBindings parent, GinjectorBindings dest) { expect(bindingFactory.getParentBinding(eq(key), eq(parent), isA(Context.class))) .andReturn(parentBinding); dest.addBinding(key, parentBinding); } private void replayAndResolve(GinjectorBindings origin, Dependency... unresolved) { expect(origin.getDependencies()).andStubReturn(TestUtils.dependencyList(unresolved)); replay(); bindingResolver.resolveBindings(origin); verify(); } public void testResolveFoundInRoot() { StandardTree tree = createExampleTree(); bind(foo(), tree.root); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveDependenciesInRoot() throws Exception { StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); bind(bar(), tree.root); bind(baz(), tree.root); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveDependenciesInRoot_InheritedByChild() throws Exception { StandardTree tree = createExampleTree(); // Bar already in root, inherited in childL. Baz already in root. Foo can still be in root. Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); bind(bar(), tree.root); bindParent(bar(), tree.root, tree.childL); bind(baz(), tree.root); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveDependenciesInOriginExposedToParent() throws Exception { StandardTree tree = createExampleTree(); // Bar is bound in the child, but exposed to the root. Foo should still be in root bind(bar(), tree.childR); bindChild(bar(), tree.root, tree.childR); bind(baz(), tree.root); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childR); replayAndResolve(tree.childR, required(Dependency.GINJECTOR, foo())); } public void testResolveDependenciesResolveInOriginExposedToParent() throws Exception { StandardTree tree = createExampleTree(); // Bar is bound in the child, but exposed to the root. Foo should still be in root bindChild(bar(), tree.root, tree.childR); bind(baz(), tree.root); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); Binding barBinding = expectCreateBinding(bar()); tree.root.addBinding(foo(), fooBinding); tree.childR.addBinding(bar(), barBinding); expectParentBinding(foo(), tree.root, tree.childR); replayAndResolve(tree.childR, required(Dependency.GINJECTOR, foo())); } public void testResolveDependenciesInChildL_ExposedToRoot() throws Exception { StandardTree tree = createExampleTree(); // Bar is bound in the child, but exposed to the root. Foo should still be in root bind(bar(), tree.childL); bindChild(bar(), tree.root, tree.childL); bind(baz(), tree.root); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveOneDependencyInChildL() throws Exception { StandardTree tree = createExampleTree(); bind(bar(), tree.root); bind(baz(), tree.childL); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); expectParentBinding(bar(), tree.root, tree.childL); // childL gets Bar from root tree.childL.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.childL, tree.childLL); // childLL gets foo from childL replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveOptionalKey_Fails() throws Exception { StandardTree tree = createExampleTree(); expect(bindingCreator.create(foo())).andThrow(new BindingCreationException("Unable to create")); replayAndResolve(tree.childLL, optional(Dependency.GINJECTOR, foo())); // No error because foo() is optional. } public void testResolveBindingWithOptionalDependency_DepFails() throws Exception{ StandardTree tree = createExampleTree(); // Baz is optional and fails to resolve Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), optional(foo(), baz())); expect(bindingCreator.create(baz())).andThrow(new BindingCreationException("Unable to create")); bind(bar(), tree.root); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childLL); // childLL gets foo from childL replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } // Tries to create Foo, which has an optional dependency on Bar, which requires Baz. // Baz can't be created, so it should create Foo, without the Bar. public void testResolveBindingWithOptionalDependencyThatFails() throws Exception { StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), optional(foo(), bar())); expectCreateBinding(bar(), required(bar(), baz())); expect(bindingCreator.create(baz())).andThrow(new BindingCreationException("Unable to create")); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveBindingWithOptionalThatDoesntBlockPosition() throws Exception { StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), optional(foo(), bar())); expectCreateBinding(bar(), required(bar(), baz())); expectCreateBinding(baz()); // Can't bar() because baz() is already bound in childLL. Therefore, bar() should not constrain // the position of foo(), and we should place it in the root. bind(baz(), tree.childLL); expect(tree.childL.isBoundLocallyInChild(baz())).andReturn(true); expect(tree.childL.getChildWhichBindsLocally(baz())).andReturn(tree.childLL); tree.root.addBinding(foo(), fooBinding); expectParentBinding(foo(), tree.root, tree.childL); replayAndResolve(tree.childL, required(Dependency.GINJECTOR, foo())); } public void testFailToCreateImplicitBinding() throws Exception { StandardTree tree = createExampleTree(); expect(bindingCreator.create(foo())).andThrow(new BindingCreationException("Unable to create")); errorManager.logError(isA(String.class), eq(foo()), isA(String.class), isA(List.class)); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testFailToResolveDependency() throws Exception { StandardTree tree = createExampleTree(); expectCreateBinding(foo(), required(foo(), bar())); expect(bindingCreator.create(bar())) .andThrow(new BindingCreationException("Unable to create")); errorManager.logError(isA(String.class), eq(bar()), isA(String.class), isA(List.class)); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testCircularDependency() throws Exception { GinjectorBindings root = createInjectorNode("root"); expectCreateBinding(bar(), required(bar(), baz())); expectCreateBinding(baz(), required(baz(), bar())); Capture<String> errorMessage = new Capture<String>(); errorManager.logError(isA(String.class), isA(Object.class), isA(Object.class)); // Intentionally use a different key, so that == won't work replayAndResolve(root, required(Dependency.GINJECTOR, bar())); } public void testCycleDetectionForBindFooToFooImpl() throws Exception { GinjectorBindings root = createInjectorNode("root"); bind(foo(), root); expectCreateBinding(fooImpl(), required(fooImpl(), bar())); expectCreateBinding(bar(), required(bar(), foo())); Capture<String> errorMessage = new Capture<String>(); errorManager.logError(EasyMock.isA(String.class), EasyMock.anyObject(), EasyMock.anyObject()); // Intentionally use a different key, so that == won't work replayAndResolve(root, required(foo(), fooImpl())); } public void testOneNode() throws Exception { GinjectorBindings root = createInjectorNode("root"); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); bind(bar(), root); bind(baz(), root); root.addBinding(foo(), fooBinding); replayAndResolve(root, required(Dependency.GINJECTOR, foo())); } public void testDependencyInOtherChild() throws Exception { // Test one of the "weird" behaviors in Guice. Foo depends on Bar and Baz. Because // Bar is bound in a sibling, we can't create Bar in the parent. Therefore, // we create Bar (and Foo) in the origin GinjectorBindings root = createInjectorNode("root"); GinjectorBindings childL = createInjectorNode("childL"); GinjectorBindings childR = createInjectorNode("childR"); setChildren(root, childL, childR); bind(baz(), root); bind(bar(), childL); expect(root.isBoundLocallyInChild(bar())).andReturn(true).anyTimes(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); Binding barBinding = expectCreateBinding(bar()); childR.addBinding(bar(), barBinding); expectParentBinding(baz(), root, childR); childR.addBinding(foo(), fooBinding); replayAndResolve(childR, required(Dependency.GINJECTOR, foo())); } public void testDepHiddenInChildBlocksResolvingInRoot() throws Exception { GinjectorBindings root = createInjectorNode("root"); GinjectorBindings child = createInjectorNode("child_module"); setChildren(root, child); bind(baz(), root); bind(bar(), child); expect(root.isBoundLocallyInChild(bar())).andReturn(true).anyTimes(); expect(root.getChildWhichBindsLocally(bar())).andReturn(child); expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); expectCreateBinding(bar()); Capture<String> errorMessage = new Capture<String>(); errorManager.logError(isA(String.class), isA(Object.class), capture(errorMessage), isA(Object.class)); // failure to create bar b/c already bound replayAndResolve(root, required(Dependency.GINJECTOR, foo())); assertTrue(errorMessage.getValue().contains("child_module")); } public void testDepHiddenInChildBlocksResolvingInRoot_NoErrorIfOptional() throws Exception { GinjectorBindings root = createInjectorNode("root"); GinjectorBindings child = createInjectorNode("child"); setChildren(root, child); bind(baz(), root); bind(bar(), child); expect(root.isBoundLocallyInChild(bar())).andReturn(true).anyTimes(); expect(root.getChildWhichBindsLocally(bar())).andReturn(child); Binding fooBinding = expectCreateBinding(foo(), required(foo(), baz()), optional(foo(), bar())); expectCreateBinding(bar()); root.addBinding(foo(), fooBinding); replayAndResolve(root, required(Dependency.GINJECTOR, foo())); } public void testManyChildren() throws Exception { GinjectorBindings root = createInjectorNode("root"); GinjectorBindings child1 = createInjectorNode("child1"); GinjectorBindings child2 = createInjectorNode("child2"); GinjectorBindings child3 = createInjectorNode("child3"); setChildren(root, child1, child2, child3); bind(bar(), child1); bindChild(bar(), root, child1); bind(baz(), child2); bindChild(baz(), root, child2); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar()), required(foo(), baz())); root.addBinding(foo(), fooBinding); expectParentBinding(foo(), root, child3); replayAndResolve(child3, required(Dependency.GINJECTOR, foo())); } public void testResolveCycleThroughProvider() throws Exception { // Foo -> Bar ->Provider<Foo> -> Foo, cycle is OK because of Provider. // Provider<Foo> is in the "unpositioned pending Foo" set. StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar())); Binding barBinding = expectCreateBinding(bar(), required(bar(), providerFoo())); Binding providerFooBinding = expectCreateBinding(providerFoo(), requiredLazy(providerFoo(), foo())); tree.root.addBinding(foo(), fooBinding); tree.root.addBinding(bar(), barBinding); tree.root.addBinding(providerFoo(), providerFooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveCycleThroughAsyncProvider() throws Exception { // Foo -> AsyncProvider<Foo> -> Foo, cycle is OK because of AsyncProvider. // AsyncProvider<Foo> is in the "unpositioned pending Foo" set. Identical to // testResolveCycleThroughProvider, but verifies that AsyncProvider is also acceptable. StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), asyncProviderFoo())); Binding providerFooBinding = expectCreateBinding( asyncProviderFoo(), requiredLazy(asyncProviderFoo(), foo())); tree.root.addBinding(foo(), fooBinding); tree.root.addBinding(asyncProviderFoo(), providerFooBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveCycleDepOfProviderBound() throws Exception { // Foo -> Provider<Bar> -> Bar -> {Foo, Baz}, Baz is bound at childL // This test makes sure that we at least ensure that Bar doesn't move higher than // *any* of it's dependencies, even after detecting a cycle. StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), providerBar())); Binding providerBarBinding = expectCreateBinding(providerBar(), requiredLazy(providerBar(), bar())); Binding barBinding = expectCreateBinding(bar(), required(bar(), foo()), required(bar(), baz())); bind(baz(), tree.childL); tree.childL.addBinding(foo(), fooBinding); tree.childL.addBinding(providerBar(), providerBarBinding); tree.childL.addBinding(bar(), barBinding); expectParentBinding(foo(), tree.childL, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveCycleDepBeforeProviderBound() throws Exception { // Foo -> {Baz, Provider<Bar>}; Provider<Bar> -> Bar -> Foo. Baz is bound at childL // Similar to the last one, although this time the already bound dependency comes before // earlier in the cycle (Before the provider). We should still make sure that Foo is // in the correct position. StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), baz()), required(foo(), providerBar())); Binding providerBarBinding = expectCreateBinding(providerBar(), requiredLazy(providerBar(), bar())); Binding barBinding = expectCreateBinding(bar(), required(bar(), foo())); bind(baz(), tree.childL); tree.childL.addBinding(foo(), fooBinding); tree.childL.addBinding(providerBar(), providerBarBinding); tree.childL.addBinding(bar(), barBinding); expectParentBinding(foo(), tree.childL, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveIndirectCycleThroughProvider() throws Exception { // Foo -> Baz -> Provider<Bar> -> Bar -> Foo StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), baz())); Binding bazBinding = expectCreateBinding(baz(), required(baz(), providerBar())); Binding providerBarBinding = expectCreateBinding( providerBar(), requiredLazy(providerBar(), bar())); Binding barBinding = expectCreateBinding(bar(), required(bar(), foo())); tree.root.addBinding(foo(), fooBinding); tree.root.addBinding(providerBar(), providerBarBinding); tree.root.addBinding(bar(), barBinding); tree.root.addBinding(baz(), bazBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } public void testResolveMultipleCycles() throws Exception { // Foo -> Bar -> Provider<Baz> -> Baz -> {Provider<Foo>, Provider<Bar>, Provider<Baz>} // Resolves multiple cycles, including a cycle that starts with a Provider StandardTree tree = createExampleTree(); Binding fooBinding = expectCreateBinding(foo(), required(foo(), bar())); Binding barBinding = expectCreateBinding(bar(), required(bar(), providerBaz())); Binding providerBazBinding = expectCreateBinding( providerBaz(), requiredLazy(providerBaz(), baz())); Binding bazBinding = expectCreateBinding(baz(), required(baz(), providerFoo()), required(baz(), providerFoo()), required(baz(), providerBar())); Binding providerFooBinding = expectCreateBinding( providerFoo(), requiredLazy(providerFoo(), foo())); Binding providerBarBinding = expectCreateBinding( providerBar(), requiredLazy(providerBar(), bar())); tree.root.addBinding(foo(), fooBinding); tree.root.addBinding(providerBar(), providerBarBinding); tree.root.addBinding(providerFoo(), providerFooBinding); tree.root.addBinding(providerBaz(), providerBazBinding); tree.root.addBinding(bar(), barBinding); tree.root.addBinding(baz(), bazBinding); expectParentBinding(foo(), tree.root, tree.childLL); replayAndResolve(tree.childLL, required(Dependency.GINJECTOR, foo())); } private Dependency required(Key<?> source, Key<?> key) { return new Dependency(source, key, SOURCE); } private Dependency optional(Key<?> source, Key<?> key) { return new Dependency(source, key, true, false, SOURCE); } private Dependency requiredLazy(Key<?> source, Key<?> key) { return new Dependency(source, key, false, true, SOURCE); } private Binding expectCreateBinding(Key<?> key, Dependency... keys) throws Exception { Binding binding = control.createMock(Binding.class); expect(bindingCreator.create(key)).andReturn(binding); Set<Dependency> requiredKeys = new HashSet<Dependency>(keys.length); Collections.addAll(requiredKeys, keys); expect(binding.getDependencies()).andReturn(requiredKeys).atLeastOnce(); bindings.add(binding); return binding; } }