How can I make apt process test source files?
When I have following setup I'm getting exception:
java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.module.TestDatabaseModule could not be loaded. Please ensure that code generation was run for this module.
and class TestDatabaseModule$$ModuleAdapter is not generated while DatabaseModule$$ModuleAdapter is generated correctly.
What am I doing wrong here?
build.gradle:
apt {
arguments {
androidManifestFile variant.processResources.manifestFile
resourcePackageName 'info.korzeniowski.walletplus'
}
}
dependencies {
// Dagger
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
}
./src/main/java/info/korzeniowski/walletplus/module/DatabaseModule.java
package info.korzeniowski.walletplus.module;
#Module(
...
)
public class DatabaseModule {
private DatabaseHelper databaseHelper;
public DatabaseModule(WalletPlus application) {
databaseHelper = OpenHelperManager.getHelper(application, DatabaseHelper.class);
}
#Provides
#Singleton
public DatabaseHelper provideDatabaseHelper() {
return databaseHelper;
}
...
}
./src/androidTest/java/info/korzeniowski/walletplus/test/module/TestDatabaseModule.java
package info.korzeniowski.walletplus.test.module;
#Module(
...
includes = DatabaseModule.class,
overrides = true
)
public class TestDatabaseModule {
#Provides
#Singleton
public DatabaseHelper provideTestDatabaseHelper() {
return new DatabaseHelper(Robolectric.application, null);
}
}
RobolectricTest.java
#Before
public void setUp() {
List<Object> modules = new ArrayList<Object>();
modules.add(new DatabaseModule((WalletPlus) Robolectric.application));
modules.add(new TestDatabaseModule());
ObjectGraph.create(modules.toArray()).inject(this);
}
The apt configuration is only for the main sources. You must also declare the annotation processor to run for test sources since it is a separate invocation of javac.
You can do so using a similar syntax:
androidTestApt "com.squareup.dagger:dagger-compiler:$daggerVersion"
Related
I'm using a custom framework headers as a compile time dependencies:
Top-level build.gradle.kts:
allprojects {
gradle.projectsEvaluated {
tasks.withType(JavaCompile::class) {
options.compilerArgs.add("-Xbootclasspath/p:sdk/framework.jar")
val newFiles = options.bootstrapClasspath?.files?.toMutableList() ?: mutableListOf<File>()
newFiles.add(File("sdk/framework.jar"))
options.bootstrapClasspath = files(newFiles.toTypedArray())
}
}
}
Module's build.gradle.kts:
compileOnly(project.files("${project.rootDir.absolutePath}/sdk/framework.jar"))
Then if I use a custom class in a «regular» code, it works fine:
val viewProxyManager: ViewProxyManager = getSystemService(Context.VIEW_PROXY_SERVICE) as ViewProxyManager
But if it's used in module's Hilt module, it fails to find the class:
#Provides
#Reusable
fun provideViewProxyManager(
#ApplicationContext
context: Context
): ViewProxyManager {
return context.getSystemService(Context.VIEW_PROXY_SERVICE) as ViewProxyManager
}
ComponentProcessingStep was unable to process 'com.package.remote.agent.app.App_HiltComponents.SingletonC' because 'android.app.ViewProxyManager' could not be resolved.
How to add my framework jar to the kapt tasks classpath?
I want to write Espresso tests for an app so I'm trying DaggerMock to
mock some external dependencies like local storage.
My Dagger setup consists of an ApplicationComponent with 3 modules (DatabaseModule, DataModule and ApplicationModule) and for the screen( a Fragment ) I want to test I have also another component which depends on ApplicationComponent.
What I have tried so far is :
#Rule public DaggerMockRule<ApplicationComponent> daggerRule =
new DaggerMockRule<>(ApplicationComponent.class, new DatabaseModule(), new DataModule(application),
new ApplicationModule(application)).set(
component -> {
MyApplication app =
(MyApplication) InstrumentationRegistry.getInstrumentation()
.getTargetContext()
.getApplicationContext();
app.setComponent(component);
});
#Rule
public final DaggerMockRule<FeedComponent> rule = new DaggerMockRule<>(
FeedComponent.class, new FeedDataSourceModule(),
new FeedDownloadImageUseCaseModule(), new FeedServiceModule(), new FeedPresenterModule(null))
.addComponentDependency(ApplicationComponent.class, new DatabaseModule(), new DataModule(application), new ApplicationModule(application))
.set(component -> localDataSource = component.localDataSource());
#Mock FeedDao feedDao;
#Mock NetworkUtils networkUtils;
#Mock FeedLocalDataSource localDataSource;
where localDataSource is actually the dependency I want to mock and it's build in FeedDataSourceModule :
#Module
public class FeedDataSourceModule {
#Provides
#FragmentScope
public FeedItemMapper providesFeedItemMapper() {
return new FeedItemMapper();
}
#Provides
#FragmentScope
public FeedLocalDataSource providesFeedLocalDataSource(FeedDao feedDao, FeedRequestDetailsDao detailsDao, FeedItemMapper mapper) {
return new FeedLocalDataSourceImpl(feedDao, detailsDao, mapper);
}
#Provides
#FragmentScope
public FeedRemoteDataSource providesFeedRemoteDataSource(FeedService feedService, FlagStateService flagStateService,
#Named("Api-Token") String apiToken, #Named("Screen-Size") String screenSize,
#Named("Account-Id") String accountId) {
return new FeedRemoteDataSourceImpl(feedService, flagStateService, apiToken, screenSize, accountId);
}
}
and also the FeedComponent with the dependency on ApplicationComponent :
#FragmentScope
#Component( dependencies = ApplicationComponent.class,
modules = {
FeedPresenterModule.class,
FeedServiceModule.class,
FeedDataSourceModule.class,
FeedDownloadImageUseCaseModule.class})
public interface FeedComponent {
#Named("Api-Token") String getApiToken();
#Named("Api-Key") String getApiKey();
FeedLocalDataSource localDataSource();
FeedRemoteDataSource remoteDataSource();
void inject(FeedFragment feedFragment);
}
With the two #Rules I posted above I can confirm that NetworkUtils indeed seems to have been mocked correctly since I have used Mockito.when() to return false value and by using a breakpoint in my code I can see the value is always false :
when(networkUtils.isOnline())
.thenReturn(false);
But this is not true for localDataSource which gives me null when I'm calling localDataSource.getFeedSorted() although I have declared :
when(localDataSource.getFeedSorted())
.thenReturn(Flowable.just(feedList));
Just in case it helps, this is how I inject the dependencies from FeedComponent :
DaggerFeedComponent.builder()
.applicationComponent(MyApplication.getApplicationComponent())
.feedPresenterModule(new FeedPresenterModule(this))
.build()
.inject(this);
Why are you using two DaggerMock rules in a test? I think you can use a single rule like in this example.
I'm trying to create a simple app with retrofit 2, dagger 2 and MVP, but I struggle with dependencies, actualy, this is the error I get after i try to rebuild the project Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
java.lang.StackOverflowError
and also in App class where I provide AppComponent: can not resolve symbol 'DaggerAppComponent'
I'll try to show you what my project looks like so someone can see the problem, First one is my AppModule which includes PresentationModule.class
#Module(includes = PresentationModule.class)
public class AppModule {
private App app;
public AppModule(App app) {
this.app = app;
}
#Provides
#Singleton
public App provideApp() {
return app;
}
}
Presentation Module looks like this:
#Module(includes = InteractorModule.class)
public class PresentationModule {
#Provides
JokePresenter providePresenter(JokeInteractor jokeInteractor) {
return new JokePresenterImpl(jokeInteractor);
}
}
And InteractorModule:
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule {
#Provides
JokeInteractor provideJokeInteractor(RetrofitService service, JokePresenter presenter) {
return new JokeInteractorImpl(service, presenter);
}
}
This is JokePresenter that has a reference to view and interactor:
public class JokePresenterImpl implements JokePresenter {
private JokeView mJokeView;
private List<String> mData = new ArrayList<>();
private String mJoke;
private JokeInteractor jokeInteractor;
public JokePresenterImpl(JokeInteractor jokeInteractor) {
this.jokeInteractor = jokeInteractor;
}
#Override
public void setView(JokeView view) {
this.mJokeView = view;
}
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke();
}
}
And JokeInteractor that has a RetrofitService and JokePresenter references:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
private JokePresenter presenter;
public JokeInteractorImpl(RetrofitService service, JokePresenter presenter) {
this.retrofitService = service;
this.presenter = presenter;
}
#Override
public void getRandomJoke() {
retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
presenter.onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
presenter.onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
presenter.onNextRandomJoke(randomJokeResponse);
}
});
}
Gradle dependencies:
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
provided 'javax.annotation:jsr250-api:1.0'
//retrofit
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.okhttp:okhttp:2.5.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
//rx java
compile 'io.reactivex:rxjava:1.1.6'
compile 'io.reactivex:rxandroid:1.2.1'
//dagger2
compile 'com.google.dagger:dagger:2.0'
provided 'org.glassfish:javax.annotation:10.0-b28'
Can someone see the problem here?
Look at the implementation of:
#Module(includes = InteractorModule.class)
public class PresentationModule
and
#Module(includes = {NetworkModule.class, PresentationModule.class})
public class InteractorModule
You have a cyclic dependency. You need to rethink your design. I propose to decouple Interactor from Presenter.
A simple solution:
Change getRandomJoke() implementation to return Observable and subscribe to it inside Presenter. And remove presenter reference from Interactor.
Interactor:
public class JokeInteractorImpl implements JokeInteractor {
private RetrofitService retrofitService;
public JokeInteractorImpl(RetrofitService service) {
this.retrofitService = service;
}
#Override
public Observable getRandomJoke() {
return retrofitService.getRandomJoke()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
}
Presenter:
#Override
public void getRandomJoke() {
mJokeView.showProgress();
jokeInteractor.getRandomJoke()
.subscribe(new Observer<RandomJokeResponse>() {
#Override
public void onCompleted() {
onRandomJokeCompleted();
}
#Override
public void onError(Throwable e) {
onError(e.getMessage());
}
#Override
public void onNext(RandomJokeResponse randomJokeResponse) {
onNextRandomJoke(randomJokeResponse);
}
});
}
Make sure that dependencies added in your gradle
implementation 'com.google.dagger:dagger-android:2.15'
implementation 'com.google.dagger:dagger-android-support:2.13'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.16'
Make sure adding AndroidSupportInjectionModule::class inside your modules.
Like this:
#Component(
modules = [
AndroidSupportInjectionModule::class,
....//Other module classes
]
)
This is what worked for me (Solution) :-
If you have any other build issues related to Dagger apart from DaggerAppComponent import issue, then make sure to fix them first, if not we will just end up wasting time.
If you fix the other Dagger issues, & then build your project.
The DaggerAppComponent will get generated, & then all you have to do is import it.
Brief info regarding the issue that I faced :-
The mistake that I did was that, I had another Dagger build issue, but I ignored it & spent my time trying to fix 'DaggerAppComponent' import problem.
Wont be relevant to you but in my case, the DI module class annotated with #Module had a single abstract method, & hence the module class had to be made abstract as well. But later accidentally I added 2 other non-abstract methods within this same abstract class. So when I fixed this & re-build the project. DaggerAppComponent was available for import.
This is all what I have in my app level build.gradle file in relation to dagger :-
Within plugins block :
id 'kotlin-kapt'
Within dependencies block :
implementation "com.google.dagger:dagger:2.43.2"
kapt "com.google.dagger:dagger-compiler:2.43.2"
(Please note that "2.43.2" was the latest available version at the time of posting this comment)
My failed attempts (These answers didn't work for me) :-
Clean > Re-build > Invalidates caches & restart.
Switching to Project view, deleting build folder & re-building the project.
Trying out various dagger versions & extra dagger dependencies that I didnt require.
I want to use Dagger injections in my robolectric tests but I have trouble with set it up. Where is error in my code sample. How can I make this work?
My main module
#Module(
includes = DatabaseModule.class,
injects = {
MainActivity.class,
}
)
public class MainModule {
private final MyApplication application;
public MainModule(MyApplication application) {
this.application = application;
}
My test module
#Module(
overrides = true,
includes = MainModule.class,
injects = {
TestMyApplication.class,
MyApplication.class
}
)
public class TestModule {
}
My production main class
public class MyApplication extends Application {
#Inject
public MyApplication() {
super();
}
#Override
public void onCreate() {
super.onCreate();
graph = ObjectGraph.create(getModules().toArray());
inject(this);
}
...
}
My robolectric test application class
public class TestMyApplication extends MyApplication {
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
return modules;
}
public void injectMocks(Object object) {
((TestMyApplication) Robolectric.application).inject(object);
}
Error:
java.lang.RuntimeException: java.lang.IllegalArgumentException: No inject registered for members/info.korzeniowski.MyApplication.TestMyApplication. You must explicitly add it to the 'injects' option in one of your modules.
When I change in My robolectric application class method to this:
#Override
protected List<Object> getModules() {
List<Object> modules = super.getModules();
modules.add(new TestModule());
return modules;
}
Result:
java.lang.RuntimeException: java.lang.IllegalStateException: Module adapter for class info.korzeniowski.walletplus.test.TestModule could not be loaded. Please ensure that code generation was run for this module.
Update
gradle.build:
compile 'com.jakewharton:butterknife:5.1.2'
def daggerVersion = '1.2.+'
apt "com.squareup.dagger:dagger-compiler:$daggerVersion"
compile "com.squareup.dagger:dagger:$daggerVersion"
compile "com.squareup:javawriter:2.2.1"
You should provide the TestModule and you are doing it right (TestModule must be in the list of modules returned from getModules). You can find solution for the second error here: Dagger example built through eclipse fails with 'Please ensure that code generation was run for this module.'
I have a Gradle app with a project structure similar to Jake Wharton's u2020:
/src
/androidTest
/debug
/main
/release
In my application class I build the Dagger graph and inject it:
MyApplication extends Application {
...
public void buildObjectGraphAndInject() {
Object[] modules = Modules.list(this);
mApplicationGraph = ObjectGraph.create(modules);
mApplicationGraph.inject(this);
}
...
}
Inside the debug sourceset, I define Modules.list() as:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new DebugAppModule()
};
}
private Modules() {
// No instances.
}
}
Inside the release sourceset, I define the same thing minus the DebugAppModule:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app)
};
}
private Modules() {
// No instances.
}
}
Deeper down my dependency graph, I create a MockRestAdapter that I can use when running the debug version:
#Module(
complete = false,
library = true,
overrides = true
)
public final class DebugApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
AndroidMockValuePersistence.install(mockRestAdapter, preferences);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(RestAdapter restAdapter, MockRestAdapter mockRestAdapter,
#IsMockMode boolean isMockMode, MockMyApi mockService) {
if (isMockMode) {
return mockRestAdapter.create(MyApi.class, mockService);
}
return restAdapter.create(MyApi.class);
}
}
But while I'm running tests, I would like to override the DebugApiModule with a TestApiModule that looks like this:
#Module(
complete = false,
library = true,
overrides = true
)
public final class TestApiModule {
#Provides #Singleton Endpoint provideEndpoint(#ApiEndpoint StringPreference apiEndpoint) {
return Endpoints.newFixedEndpoint(apiEndpoint.get());
}
#Provides #Singleton MockRestAdapter provideMockRestAdapter(RestAdapter restAdapter, SharedPreferences preferences) {
MockRestAdapter mockRestAdapter = MockRestAdapter.from(restAdapter);
mockRestAdapter.setDelay(0);
mockRestAdapter.setErrorPercentage(0);
mockRestAdapter.setVariancePercentage(0);
return mockRestAdapter;
}
#Provides #Singleton MyApi provideMyApi(MockRestAdapter mockRestAdapter, MockHnApi mockService) {
return mockRestAdapter.create(MyApi.class, mockService);
}
}
What's the best way to accomplish this? Do I need to create a TestAppModule like this:
public final class Modules {
public static Object[] list(MyApplication app) {
return new Object[] {
new AppModule(app),
new TestAppModule()
};
}
private Modules() {
// No instances.
}
}
And replace all of the DebugFooModule with TestFooModules? If so, how do I get around the fact that Modules.java is duplicated? Or am I way off base?
EDIT: SOLUTION
What I ended up doing is replacing the Application-level graph (where the MockRestAdapter gets created) during my test setUp
protected void setUp() throws Exception {
super.setUp();
HnApp app = HnApp.getInstance();
app.buildObjectGraphAndInject(TestModules.list(app));
getActivity();
}
What I've done this far (and I'm not entirely sure this will scale yet) is create a static test module class in my tests.
#Module(
injects = {
SharedPreferences.class
})
static class TestModule {
#Provides #Singleton SharedPreferences provideSharedPreferences(){
return Mockito.mock(SharedPreferences.class);
}
}
And in the setup() method I inject the test module
#Before
public void setUp(){
ObjectGraph.create(new TestModule()).inject(this);
}
I then #Inject the mocked class:
#Inject SharedPreferences sharedPreferences;