/*
* 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);
}
}