/**************************************************************************** * Sangria * * Copyright (C) 2014 Tavian Barnes * * * * 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.tavianator.sangria.lazy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.*; import javax.inject.Inject; import javax.inject.Qualifier; import javax.inject.Singleton; import com.google.inject.AbstractModule; import com.google.inject.Binding; import com.google.inject.CreationException; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.Module; import com.google.inject.ProvidedBy; import com.google.inject.Provider; import com.google.inject.spi.DefaultBindingTargetVisitor; import com.google.inject.spi.Element; import com.google.inject.spi.Elements; import com.google.inject.util.Providers; import org.junit.Test; import static com.tavianator.sangria.test.SangriaMatchers.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** * Tests for {@link Lazy} injection. * * @author Tavian Barnes (tavianator@tavianator.com) * @version 1.3 * @since 1.2 */ public class LazyTest { @Retention(RetentionPolicy.RUNTIME) @Qualifier private @interface Simple { } @ProvidedBy(CountingProvider.class) private interface Abstract { } @Singleton private static class CountingProvider implements Provider { int count = 0; private final Provider impl; @Inject CountingProvider() { this.impl = new Provider() { @Override public Abstract get() { return new Abstract() { }; } }; } CountingProvider(Abstract instance) { this.impl = Providers.of(instance); } @Override public Abstract get() { ++count; return impl.get(); } } private static class HasLazy { final Lazy lazy; @Inject HasLazy(Lazy lazy) { this.lazy = lazy; } } private static class HasSimpleLazy extends HasLazy { @Inject HasSimpleLazy(@Simple Lazy lazy) { super(lazy); } } private void test(Injector injector, Class type) { CountingProvider provider = injector.getInstance(CountingProvider.class); HasLazy hasLazy = injector.getInstance(type); assertThat(provider.count, equalTo(0)); Abstract a = hasLazy.lazy.get(); assertThat(provider.count, equalTo(1)); assertThat(hasLazy.lazy.get(), sameInstance(a)); assertThat(provider.count, equalTo(1)); hasLazy = injector.getInstance(type); assertThat(provider.count, equalTo(1)); a = hasLazy.lazy.get(); assertThat(provider.count, equalTo(2)); assertThat(hasLazy.lazy.get(), sameInstance(a)); assertThat(provider.count, equalTo(2)); } @Test public void testJustInTime() { test(Guice.createInjector(), HasLazy.class); } private static final Module EXPLICIT_MODULE = new AbstractModule() { @Override protected void configure() { bind(Abstract.class) .toProvider(CountingProvider.class); LazyBinder.create(binder()) .bind(Abstract.class); bind(HasLazy.class); } }; @Test public void testExplicitBindings() { test(Guice.createInjector(EXPLICIT_MODULE), HasLazy.class); } private static final Module BIND_SEPARATELY_MODULE = new AbstractModule() { @Override protected void configure() { bind(Abstract.class) .annotatedWith(Simple.class) .toProvider(CountingProvider.class); LazyBinder.create(binder()) .bind(Abstract.class) .annotatedWith(Simple.class); bind(HasSimpleLazy.class); } }; @Test public void testBindSeparately() { test(Guice.createInjector(BIND_SEPARATELY_MODULE), HasSimpleLazy.class); } private static final Module BIND_TOGETHER_MODULE = new AbstractModule() { @Override protected void configure() { LazyBinder.create(binder()) .bind(Abstract.class) .annotatedWith(Simple.class) .toProvider(CountingProvider.class); bind(HasSimpleLazy.class); } }; @Test public void testBindTogether() { test(Guice.createInjector(BIND_TOGETHER_MODULE), HasSimpleLazy.class); } @Test public void testBestPractices() { Module module = new AbstractModule() { @Override protected void configure() { install(EXPLICIT_MODULE); install(BIND_SEPARATELY_MODULE); install(BIND_TOGETHER_MODULE); } }; assertThat(module, is(atomic())); assertThat(module, followsBestPractices()); } @Test public void testNull() { Module module = new AbstractModule() { @Override protected void configure() { bind(CountingProvider.class) .toInstance(new CountingProvider(null)); } }; test(Guice.createInjector(module), HasLazy.class); } @Test(expected = CreationException.class) public void testMissingBinding() { Guice.createInjector(new AbstractModule() { @Override protected void configure() { LazyBinder.create(binder()) .bind(Abstract.class) .annotatedWith(Simple.class); } }); } private static class TestVisitor extends DefaultBindingTargetVisitor implements LazyBindingVisitor { @Override public Boolean visit(LazyBinding binding) { assertThat(binding.getTargetKey().equals(new Key(Simple.class) { }), is(true)); return true; } @Override protected Boolean visitOther(Binding binding) { return false; } } private boolean visit(Binding binding) { return binding.acceptTargetVisitor(new TestVisitor()); } @Test public void testExtensionSpi() { Module module = new AbstractModule() { @Override protected void configure() { // TODO: Expose the SPI for unqualified bindings LazyBinder.create(binder()) .bind(Abstract.class) .annotatedWith(Simple.class) .toProvider(CountingProvider.class); } }; List elements = Elements.getElements(module); int passed = 0; for (Element element : elements) { if (element instanceof Binding) { if (visit((Binding)element)) { ++passed; } } } assertThat(passed, equalTo(1)); Injector injector = Guice.createInjector(Elements.getModule(elements)); assertThat(visit(injector.getBinding(new Key>(Simple.class) { })), is(true)); assertThat(visit(injector.getBinding(new Key(Simple.class) { })), is(false)); } }