Closed mattinger closed 3 years ago
I haven't heard of daggermock before. I don't see us implementing what you're asking for. However, Anvil provides extension points to create your own custom code generator and I think what you're asking for could be solved this way. Please take a look at the documentation.
I've taken a brief look and I'm wondering if this will even work for me. To be honest, we need to hook into the generation of the actual class annotated with @Component so that we can create the inner @Component.Builder or @Component.Factory interface. Is this even possible with the compiler api?
Component generation is done via Dagger, after Anvil's processing is all done.
Am I understanding this correctly? If you wanted to create a binding for MyService
and MyServiceImpl
, you might want to write this:
@ContributesBinding(MyScope::class)
class MyServiceImpl @Inject constructor(...) : MyService
You can't override this "module" because there isn't really a module.
* Technically, there is a module. Anvil creates a single, potentially giant module with all the bindings for each component. But if I understand DaggerMock correctly, it creates mocks for every binding in the module, so you wouldn't want to do that here.
Instead of using @ContributesBinding
for that binding, you could just create a normal module:
class MyServiceImpl @Inject constructor() : MyService
@Module
@ContributesTo(MyScope::class)
interface MyServiceModule {
@get:Binds
val MyServiceImpl.bindMyService: MyService
}
And then DaggerMock and Dagger should work normally.
DaggerMock supplies the mock bindings using the module overrides, but Anvil does the same thing using the "replace" functionality referenced in @Contributes___
annotations.
Instead of creating normal @Module
interfaces for bindings in production code, you can also just create fake/mock modules:
// in normal production code
@ContributesBinding(MyScope::class)
class MyServiceImpl @Inject constructor(...) : MyService
// in the test directory, or a test-specific module
@Module
@ContributesTo(MyScope::class, replaces = [MyServiceImpl::class])
object FakeServiceModule {
@Provides
@Singleton // the singleton scope is important
fun provideMockService(): MyService = mockk<MyService>(relaxed = true) // taking liberties here and using MockK
}
With the above code, you don't need DaggerMock. So long as FakeServiceModule
is in its classpath somewhere, you could just write something like this:
// Make this a communal thing per-module.
// For each new test class, add a new overloaded inject(...) function
@Singleton
@MergeComponent(MyScope::class)
interface TestsComponent {
fun inject(test: RepositoryTest)
}
class RepositoryTest {
// subject under test
@Inject lateinit var repository: Repository
// mocked
@Inject lateinit var mockService: MyService
@Before fun before() {
DaggerTestsComponent.factory()
.create()
.inject(this)
}
@Test fun `testing things`() {
every { mockService.getStuff() } returns ...
repository.getStuff() shouldBe ...
}
}
I've taken a brief look and I'm wondering if this will even work for me. To be honest, we need to hook into the generation of the actual class annotated with @component so that we can create the inner @Component.Builder or @Component.Factory interface. Is this even possible with the compiler api?
I'd suggest doing what @RBusarow suggested above: using Anvil's replaces feature. We rely on that heavily in tests.
To answer your question: The simple API that Anvil exposes through its CodeGenerator
interface doesn't allow you to modify code. It only allows you to generate new classes in new files. There are compiler APIs to modify existing code, but Anvil doesn't expose them and makes little use of them internally. You'd be on your own to create your own compiler plugin.
@vRallev I like the replaces feature, but would that even work in an espresso test where you are building two separate apk files? Would it even consider any modules specified in the androidTest source root when building the application apk?
You're supposed to replace bindings from the main
source set in the androidTest
source set. Not the other way around.
Closing this for now.
Im trying to use anvil with daggermock as a testing framework. However, the issue i'm running into is that daggermock doesn't really like Component.Factory, which is understandable. So i would then use a Component.Builder. However, because the list of modules is not known to me when writing the code, I can only create a builder which will internally create all the required modules:
Due to this, daggermock can't override the modules because you can't create an instance and pass it to the builder (which is my understanding of how daggermock works).
What i'd love is to be able to generate a builder in anvil from something like this:
And have anvil recognize that, and create a full fledged builder with setters for all the collected modules.