/* * Copyright 2016 Kejun Xia * * 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.shipdream.lib.poke; import com.shipdream.lib.poke.exception.CircularDependenciesException; import com.shipdream.lib.poke.exception.PokeException; import com.shipdream.lib.poke.exception.ProvideException; import com.shipdream.lib.poke.exception.ProviderConflictException; import com.shipdream.lib.poke.exception.ProviderMissingException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.inject.Inject; public class TestComponentManagement extends BaseTestCases { private Graph graph; private Component rootComponent; class Car { @MyInject Wheel wheel; } interface Wheel { } static class Wheel15Inch implements Wheel { } static class Wheel17Inch implements Wheel { } static class Wheel19Inch implements Wheel { } interface Engine{ } static class EngineV4 implements Engine{ } static class EngineV6 implements Engine { } @Before public void setUp() throws Exception { rootComponent = new Component("AppSingleton"); graph = new Graph(); graph.setRootComponent(rootComponent); } @Test public void graph_should_use_corresponding_sub_components_scope_to_inject_unscoped() throws PokeException { Component childCom = new Component(false); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); rootComponent.attach(childCom); Car car = new Car(); graph.inject(car, MyInject.class); Car car2 = new Car(); graph.inject(car2, MyInject.class); Assert.assertTrue(car.wheel != car2.wheel); } @Test public void graph_should_use_corresponding_sub_components_scope_to_inject_scoped() throws PokeException { Component childCom = new Component("SubScope", true); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); rootComponent.attach(childCom); Car car = new Car(); graph.inject(car, MyInject.class); Car car2 = new Car(); graph.inject(car2, MyInject.class); Assert.assertTrue(car.wheel == car2.wheel); } @Test public void graph_is_able_to_inject_from_new_attached_sub_components() throws PokeException { Component childCom = new Component(); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); Car car = new Car(); boolean hasProviderMissingException = false; try { graph.inject(car, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); Assert.assertNull(car.wheel); rootComponent.attach(childCom); graph.inject(car, MyInject.class); Assert.assertNotNull(car.wheel); } @Test public void graph_unable_inject_from_new_detached_sub_components() throws PokeException { Component childCom = new Component(); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); Car car = new Car(); rootComponent.attach(childCom); graph.inject(car, MyInject.class); Assert.assertNotNull(car.wheel); rootComponent.detach(childCom); boolean hasProviderMissingException = false; try { graph.inject(car, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); } @Test public void graph_is_able_to_inject_from_sub_component_new_added_provider() throws PokeException { Component childCom = new Component(); Car car = new Car(); boolean hasProviderMissingException = false; try { graph.inject(car, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); Assert.assertNull(car.wheel); rootComponent.attach(childCom); hasProviderMissingException = false; try { graph.inject(car, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); Assert.assertNull(car.wheel); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); graph.inject(car, MyInject.class); Assert.assertNotNull(car.wheel); } @Test public void graph_is_unable_to_inject_from_sub_component_with_removed_provider() throws PokeException { Component childCom = new Component(); Car car1 = new Car(); rootComponent.attach(childCom); Provider<Wheel> provider = new ProviderByClassType(Wheel.class, Wheel17Inch.class); Assert.assertNull(provider.getComponent()); childCom.register(provider); Assert.assertNotNull(provider.getComponent()); graph.inject(car1, MyInject.class); Assert.assertNotNull(car1.wheel); childCom.unregister(Wheel.class, null); Assert.assertNull(provider.getComponent()); Car car2 = new Car(); boolean hasProviderMissingException = false; try { graph.inject(car2, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); Assert.assertNull(car2.wheel); } @Test public void should_clear_overriding_providers_from_detached_child_component_correctly() throws PokeException { Graph graph = new Graph(); graph.setRootComponent(rootComponent); rootComponent.register(new Object(){ @Provides public Engine engine(){ return new EngineV6(); } }); Component childCom = new Component("MyScope"); childCom.register(new Object() { @Provides public Engine engine(){ return new EngineV4(); } @Provides public Wheel wheel() { return new Wheel15Inch(); } }); rootComponent.attach(childCom, true); Car car = new Car(); graph.inject(car, MyInject.class); Assert.assertTrue(car.wheel instanceof Wheel15Inch); rootComponent.detach(childCom); childCom = new Component("MyScope2"); rootComponent.attach(childCom, true); childCom.register(new Object() { @Provides public Wheel wheel() { return new Wheel17Inch(); } }); graph.inject(car, MyInject.class); Assert.assertTrue(car.wheel instanceof Wheel17Inch); rootComponent.detach(childCom); childCom = new Component("MyScope3"); rootComponent.attach(childCom, true); childCom.register(new Object() { @Provides public Wheel wheel() { return new Wheel19Inch(); } }); car = new Car(); graph.inject(car, MyInject.class); Assert.assertTrue(car.wheel instanceof Wheel19Inch); } @Test public void graph_is_able_to_inject_from_reattached_sub_component() throws PokeException { Component childCom = new Component("MyScope"); rootComponent.attach(childCom); childCom.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); rootComponent.detach(childCom); Graph graph1 = new Graph(); graph1.setRootComponent(childCom); Car car = new Car(); boolean hasProviderMissingException = false; try { graph.inject(car, MyInject.class); } catch (ProviderMissingException e) { hasProviderMissingException = true; } Assert.assertTrue(hasProviderMissingException); Assert.assertNull(car.wheel); graph1.inject(car, MyInject.class); Assert.assertNotNull(car.wheel); rootComponent.attach(childCom); Car car2 = new Car(); graph.inject(car2, MyInject.class); Assert.assertNotNull(car2.wheel); Assert.assertTrue(car2.wheel == car.wheel); } @Test(expected = Component.MismatchDetachException.class) public void should_throw_exception_when_detach_wrong_child_component() throws PokeException { Component childCom1 = new Component(); Component parentCom = new Component(); parentCom.attach(childCom1); rootComponent.detach(childCom1); } @Test(expected = Component.MismatchDetachException.class) public void should_throw_exception_when_detach_orphan_child_component() throws PokeException { Component childCom1 = new Component(); rootComponent.detach(childCom1); } @Test public void should_throw_exception_when_set_non_root_component_to_graph() throws PokeException { Graph g = new Graph(); Component component = new Component(); boolean exp = false; Component parent = new Component(); parent.attach(component); try { g.setRootComponent(component); } catch (Graph.IllegalRootComponentException e) { exp = true; } Assert.assertTrue(exp); //OK to set parent component which is unattached component g.setRootComponent(parent); } @Test public void should_throw_exception_add_attached_child_component() throws PokeException { Graph g = new Graph(); Component component = new Component(); g.setRootComponent(component); boolean exp = false; Component parent = new Component(); parent.attach(component); try { rootComponent.attach(component); } catch (Component.MultiParentException e) { exp = true; } Assert.assertTrue(exp); rootComponent.attach(parent); } @Test public void should_inject_from_sub_component_with_multi_level_depth() throws PokeException { Graph g = new Graph(); Component root = new Component(); g.setRootComponent(root); Component c1 = new Component(); Component c2 = new Component("c2"); c2.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); root.attach(c1); c1.attach(c2); Car car1 = new Car(); g.inject(car1, MyInject.class); Assert.assertNotNull(car1.wheel); c1.detach(c2); Car car2 = new Car(); boolean car2Exp = false; try { g.inject(car2, MyInject.class); } catch (ProviderMissingException e) { car2Exp = true; } Assert.assertTrue(car2Exp); Assert.assertNull(car2.wheel); root.attach(c2); Car car3 = new Car(); g.inject(car3, MyInject.class); Assert.assertTrue(car1.wheel == car3.wheel); Assert.assertNotNull(car3.wheel); } @Test public void should_throw_conflict_exception_when_add_conflicting_provider_to_child_component() throws PokeException { Graph g = new Graph(); Component root = new Component(); g.setRootComponent(root); root.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); Component c2 = new Component("c2"); root.attach(c2); boolean exp = false; try { c2.register(new ProviderByClassType(Wheel.class, Wheel17Inch.class)); } catch (ProviderConflictException e) { exp = true; } Assert.assertTrue(exp); } @Test public void test_component_constructor() { Component c1 = new Component("A"); Assert.assertNotNull(c1.getCache()); Assert.assertEquals("A", c1.getName()); Component c2 = new Component("B", false); Assert.assertNull(c2.getCache()); Assert.assertEquals("B", c2.getName()); Component c3 = new Component(false); Assert.assertNull(c3.getCache()); Assert.assertEquals(null, c3.getName()); Component c4 = new Component(); Assert.assertNotNull(c4.getCache()); Assert.assertEquals(null, c4.getName()); } @Test public void should_detect_provider_conflict_when_attaching_child_component() throws ProviderConflictException { Component c1 = new Component("A"); c1.register(new ProviderByClassType(String.class, String.class)); Component c2 = new Component(); c2.register(new ProviderByClassType(String.class, String.class)); boolean exp = false; try { c1.attach(c2); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertTrue(exp); exp = false; try { c2.attach(c1); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertTrue(exp); } static abstract class Phone { } static class iPhone extends Phone { } static class Galaxy extends Phone { } static class Note extends Phone { } static class Nexus extends Phone { } class Store { @MyInject Phone phone; } @Test public void should_use_overridden_components_and_not_throw_exception() throws PokeException { Graph graph = new Graph(); Component cIphone = new Component("iPhone"); cIphone.register(new ProviderByClassType(Phone.class, iPhone.class)); Component cGalaxy = new Component("Galaxy"); cGalaxy.register(new ProviderByClassType(Phone.class, Galaxy.class)); Component cNote = new Component("Note"); cNote.register(new ProviderByClassType(Phone.class, Note.class)); Component cNexus = new Component("Nexus"); cNexus.register(new ProviderByClassType(Phone.class, Nexus.class)); graph.setRootComponent(cIphone); Store store = new Store(); graph.inject(store, MyInject.class); Assert.assertEquals(iPhone.class, store.phone.getClass()); boolean exp = false; try { cIphone.attach(cGalaxy, true); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertFalse(exp); graph.inject(store, MyInject.class); Assert.assertEquals(Galaxy.class, store.phone.getClass()); try { cIphone.attach(cNote, true); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertFalse(exp); graph.inject(store, MyInject.class); Assert.assertEquals(Note.class, store.phone.getClass()); try { cGalaxy.attach(cNexus, true); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertFalse(exp); graph.inject(store, MyInject.class); Assert.assertEquals(Nexus.class, store.phone.getClass()); cIphone.detach(cNote); graph.inject(store, MyInject.class); Assert.assertEquals(Nexus.class, store.phone.getClass()); cGalaxy.detach(cNexus); graph.inject(store, MyInject.class); Assert.assertEquals(Galaxy.class, store.phone.getClass()); cIphone.detach(cGalaxy); graph.inject(store, MyInject.class); Assert.assertEquals(iPhone.class, store.phone.getClass()); Component c0 = new Component(); graph.setRootComponent(c0); c0.attach(cIphone); graph.inject(store, MyInject.class); Assert.assertEquals(iPhone.class, store.phone.getClass()); } @Test public void should_roll_back_root_component_keys_when_attaching_child_component_with_conflicts() throws ProviderConflictException, ProviderMissingException, Graph.IllegalRootComponentException { Component c1 = new Component("A"); c1.register(new ProviderByClassType(Integer.class, Integer.class)); Component c2 = new Component(); c2.register(new ProviderByClassType(Integer.class, Integer.class)); c2.register(new ProviderByClassType(String.class, String.class)); boolean exp = false; try { c1.attach(c2); } catch (ProviderConflictException e) { exp = true; } catch (Component.MultiParentException e) { } Assert.assertTrue(exp); Graph graph = new Graph(); graph.setRootComponent(c1); boolean missingExp = false; try { graph.reference(String.class, null, Inject.class); } catch (ProviderMissingException e) { missingExp = true; } catch (ProvideException e) { e.printStackTrace(); } catch (CircularDependenciesException e) { e.printStackTrace(); } Assert.assertTrue(missingExp); } }