/* * Copyright (C) 2015 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 dagger.internal.codegen; import com.google.common.collect.ImmutableList; import com.google.testing.compile.JavaFileObjects; import javax.tools.JavaFileObject; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import static com.google.common.truth.Truth.assertAbout; import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources; @RunWith(JUnit4.class) public final class SubcomponentValidationTest { @Test public void factoryMethod_missingModulesWithParameters() { JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent newChildComponent();", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = ModuleWithParameters.class)", "interface ChildComponent {", " Object object();", "}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ModuleWithParameters", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "final class ModuleWithParameters {", " private final Object object;", "", " ModuleWithParameters(Object object) {", " this.object = object;", " }", "", " @Provides Object object() {", " return object;", " }", "}"); assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile, moduleFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining( "test.ChildComponent requires modules which have no visible default constructors. " + "Add the following modules as parameters to this method: " + "test.ModuleWithParameters") .in(componentFile).onLine(7); } @Test public void factoryMethod_nonModuleParameter() { JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent newChildComponent(String someRandomString);", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface ChildComponent {}"); assertAbout(javaSources()).that(ImmutableList.of(componentFile, childComponentFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining( "Subcomponent factory methods may only accept modules, but java.lang.String is not.") .in(componentFile).onLine(7).atColumn(43); } @Test public void factoryMethod_duplicateParameter() { JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "", "@Module", "final class TestModule {}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent newChildComponent(TestModule testModule1, TestModule testModule2);", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = TestModule.class)", "interface ChildComponent {}"); assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining( "A module may only occur once an an argument in a Subcomponent factory method, " + "but test.TestModule was already passed.") .in(componentFile).onLine(7).atColumn(71); } @Test public void factoryMethod_superflouousModule() { JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "", "@Module", "final class TestModule {}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent newChildComponent(TestModule testModule);", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "interface ChildComponent {}"); assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining( "test.TestModule is present as an argument to the test.ChildComponent factory method, but " + "is not one of the modules used to implement the subcomponent.") .in(componentFile).onLine(7); } @Test public void missingBinding() { JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.TestModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "final class TestModule {", " @Provides String provideString(int i) {", " return Integer.toString(i);", " }", "}"); JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.TestComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface TestComponent {", " ChildComponent newChildComponent();", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = TestModule.class)", "interface ChildComponent {", " String getString();", "}"); assertAbout(javaSources()).that(ImmutableList.of(moduleFile, componentFile, childComponentFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining( "java.lang.Integer cannot be provided without an @Inject constructor or from an " + "@Provides-annotated method"); } @Test public void subcomponentOnConcreteType() { JavaFileObject subcomponentFile = JavaFileObjects.forSourceLines("test.NotASubcomponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent", "final class NotASubcomponent {}"); assertAbout(javaSources()).that(ImmutableList.of(subcomponentFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining("interface"); } @Test public void scopeMismatch() { JavaFileObject componentFile = JavaFileObjects.forSourceLines("test.ParentComponent", "package test;", "", "import dagger.Component;", "import javax.inject.Singleton;", "", "@Component", "@Singleton", "interface ParentComponent {", " ChildComponent childComponent();", "}"); JavaFileObject subcomponentFile = JavaFileObjects.forSourceLines("test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = ChildModule.class)", "interface ChildComponent {", " Object getObject();", "}"); JavaFileObject moduleFile = JavaFileObjects.forSourceLines("test.ChildModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "import javax.inject.Singleton;", "", "@Module", "final class ChildModule {", " @Provides @Singleton Object provideObject() { return null; }", "}"); assertAbout(javaSources()).that(ImmutableList.of(componentFile, subcomponentFile, moduleFile)) .processedWith(new ComponentProcessor()) .failsToCompile() .withErrorContaining("@Singleton"); } @Test public void delegateFactoryNotCreatedForSubcomponentWhenProviderExistsInParent() { JavaFileObject parentComponentFile = JavaFileObjects.forSourceLines( "test.ParentComponent", "package test;", "", "import dagger.Component;", "", "@Component", "interface ParentComponent {", " ChildComponent childComponent();", " Dep1 getDep1();", " Dep2 getDep2();", "}"); JavaFileObject childComponentFile = JavaFileObjects.forSourceLines( "test.ChildComponent", "package test;", "", "import dagger.Subcomponent;", "", "@Subcomponent(modules = ChildModule.class)", "interface ChildComponent {", " Object getObject();", "}"); JavaFileObject childModuleFile = JavaFileObjects.forSourceLines( "test.ChildModule", "package test;", "", "import dagger.Module;", "import dagger.Provides;", "", "@Module", "final class ChildModule {", " @Provides Object provideObject(A a) { return null; }", "}"); JavaFileObject aFile = JavaFileObjects.forSourceLines( "test.A", "package test;", "", "import javax.inject.Inject;", "", "final class A {", " @Inject public A(NeedsDep1 a, Dep1 b, Dep2 c) { }", " @Inject public void methodA() { }", "}"); JavaFileObject needsDep1File = JavaFileObjects.forSourceLines( "test.NeedsDep1", "package test;", "", "import javax.inject.Inject;", "", "final class NeedsDep1 {", " @Inject public NeedsDep1(Dep1 d) { }", "}"); JavaFileObject dep1File = JavaFileObjects.forSourceLines( "test.Dep1", "package test;", "", "import javax.inject.Inject;", "", "final class Dep1 {", " @Inject public Dep1() { }", " @Inject public void dep1Method() { }", "}"); JavaFileObject dep2File = JavaFileObjects.forSourceLines( "test.Dep2", "package test;", "", "import javax.inject.Inject;", "", "final class Dep2 {", " @Inject public Dep2() { }", " @Inject public void dep2Method() { }", "}"); JavaFileObject componentGeneratedFile = JavaFileObjects.forSourceLines( "DaggerParentComponent", "package test;", "", "import dagger.MembersInjector;", "import javax.annotation.Generated;", "import javax.inject.Provider;", "", "@Generated(\"dagger.internal.codegen.ComponentProcessor\")", "public final class DaggerParentComponent implements ParentComponent {", " private MembersInjector<Dep1> dep1MembersInjector;", " private Provider<Dep1> dep1Provider;", " private MembersInjector<Dep2> dep2MembersInjector;", " private Provider<Dep2> dep2Provider;", "", " private DaggerParentComponent(Builder builder) { ", " assert builder != null;", " initialize(builder);", " }", "", " public static Builder builder() { ", " return new Builder();", " }", "", " public static ParentComponent create() { ", " return builder().build();", " }", "", " @SuppressWarnings(\"unchecked\")", " private void initialize(final Builder builder) { ", " this.dep1MembersInjector = Dep1_MembersInjector.create();", " this.dep1Provider = Dep1_Factory.create(dep1MembersInjector);", " this.dep2MembersInjector = Dep2_MembersInjector.create();", " this.dep2Provider = Dep2_Factory.create(dep2MembersInjector);", " }", "", " @Override", " public Dep1 getDep1() { ", " return dep1Provider.get();", " }", "", " @Override", " public Dep2 getDep2() { ", " return dep2Provider.get();", " }", "", " @Override", " public ChildComponent childComponent() { ", " return new ChildComponentImpl();", " }", "", " public static final class Builder {", " private Builder() { ", " }", " ", " public ParentComponent build() { ", " return new DaggerParentComponent(this);", " }", " }", "", " private final class ChildComponentImpl implements ChildComponent {", " private final ChildModule childModule;", " private MembersInjector<A> aMembersInjector;", " private Provider<NeedsDep1> needsDep1Provider;", " private Provider<A> aProvider;", " private Provider<Object> provideObjectProvider;", " ", " private ChildComponentImpl() { ", " this.childModule = new ChildModule();", " initialize();", " }", "", " @SuppressWarnings(\"unchecked\")", " private void initialize() { ", " this.aMembersInjector = A_MembersInjector.create();", " this.needsDep1Provider = NeedsDep1_Factory.create(", " DaggerParentComponent.this.dep1Provider);", " this.aProvider = A_Factory.create(", " aMembersInjector,", " needsDep1Provider,", " DaggerParentComponent.this.dep1Provider,", " DaggerParentComponent.this.dep2Provider);", " this.provideObjectProvider = ChildModule_ProvideObjectFactory.create(", " childModule, aProvider);", " }", " ", " @Override", " public Object getObject() { ", " return provideObjectProvider.get();", " }", " }", "}"); assertAbout(javaSources()) .that( ImmutableList.of( parentComponentFile, childComponentFile, childModuleFile, aFile, needsDep1File, dep1File, dep2File)) .processedWith(new ComponentProcessor()) .compilesWithoutError() .and() .generatesSources(componentGeneratedFile); } }