Я разрабатываю приложение Android с использованием Kotlin. Я пишу интеграционные тесты для моего приложения. Теперь у меня возникла проблема с имитацией внедренного класса зависимостей, который вводится с помощью теста Dagger 2, если вызывается метод смоделированного объекта. Ниже приведен мой код.
У меня есть класс TestAppComponent со следующим кодом
@Singleton
@Component(modules = [ TestAppModule::class ])
interface TestAppComponent
{
fun inject(app: ApplicationController)
fun inject(app: MockApplicationController)
}
Как вы можете видеть в классе, у меня есть два метода ввода. Один для ApplicationController, который в основном является классом приложения, используемым для реального приложения. Еще один контроллер MockAppllication, который также является классом приложения, но используется для тестов.
Ниже приведена реализация класса ApplicationController
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
@Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
Как видно из кода, Я инициализирую класс Dagger AppComponent в методе onCreate класса приложения. Затем введите класс зависимости.
Это реализация моего класса MockApplicationController, который используется для тестов.
class MockApplicationController: ApplicationController()
{
private fun initDagger(app: MockApplicationController): AppComponent = DaggerTestAppComponent
.builder()
.testAppModule(TestAppModule(app))
.build()
override fun onCreate() {
super.onCreate()
//override the dagger app component appComponent
this.appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
Это мой класс активности.
class MainActivity: AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?) {
//rest of the code
button_save_file.setOnClickListener {
//rest of the code
ApplicationController.instance.fileService.saveFile(filePath)
}
}
}
Как вы можете видеть, в основном то, что я делаю в классе активности, это то, что я просто вызываю saveFile интерфейса IFileService.
Ниже приведено определение интерфейса IFileService
interface IFileService
{
fun saveFile(path: String)
}
Есть два класса, которые реализуют IFileService. Одним из них является класс FakeFileService, который будет использоваться для тестов, а другим - класс FileService, который будет использоваться для реального приложения.
Ниже приведена реализация класса FakeFileService
class FakeFileService: IFileService
{
fun saveFile(path: String)
{
//literally, this is doing nothing since we will only test that if the method is called
}
}
I также создал два класса для модулей приложения Dagger. Один для тестов, а другой для реального приложения.
Ниже приведена реализация класса TestAppModule, который будет использоваться для тестов.
@Module
open class TestAppModule (private val app: Application) {
private var fileService: IFileService? = null
@Singleton
@Provides
fun provideContext(): Context = app
//to override injecting the Mockito mock instance
fun injectFileService(fileService: IFileService) {
this.fileService = fileService
}
@Singleton
@Provides
open fun fileService(): IFileService {
if (this.fileService != null) {
return this.fileService as IFileService
}
return FakeFileService()
}
}
Обратите внимание на метод injectFileService. Я создал этот метод для внедрения в тесты фиктивного объекта / экземпляра с использованием Mockito.
Это мой тестовый класс
@RunWith(AndroidJUnit4::class)
@LargeTest
class CameraTest: TestBuilder()
{
@get:Rule
var mainActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule<MainActivity>(CameraActivity::class.java, true, false)
private lateinit var fileServiceMock: IFileIOService
@Before
fun setUp() {
this.fileServiceMock = mock(IFileService::class.java)
MockitoAnnotations.initMocks(this)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val app = instrumentation.targetContext.applicationContext as MockApplicationController
var testModule = TestAppModule(app)
testModule.injectFileService(this.fileServiceMock)
app.appComponent = DaggerTestAppComponent
.builder()
.testAppModule(testModule)
.build()
app.appComponent.inject(app)
}
@Test
fun itSavesFileWhenButtonIsClicked() {
Intents.init()
var targetContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
var intent: Intent = Intent(targetContext, MainActivity::class.java)
this.mainActivityRule.launchActivity(intent)
onView(withId(R.id.button_save_file)).perform(click())
verify(this.fileServiceMock).saveFile(any())
Intents.release()
}
}
Как вы можете видеть, в моем тестовом классе я пытаюсь внедрить проверенную версию объекта IFileService до запуска теста. Затем в моем тесте я утверждаю, что метод выполняется. Когда я запускаю тест, он жалуется, что «ноль взаимодействий с этим макетом». Как я могу это исправить? Что не так с моим кодом и как я могу это исправить?
Это мой класс ApplicationController
open class ApplicationController: Application()
{
lateinit var appComponent: AppComponent
private fun initDagger(app: ApplicationController): AppComponent = DaggerAppComponent
.builder()
.appModule(AppModule(app)).build()
@Inject
lateinit var fileService: IFileService
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
instance = this
this.appComponent.inject(this)
}
}
Когда я помещаю loggin в каждый метод onCreate классов контроллера приложения и метод @Before они вызываются в следующем порядке.
- ApplicationController
- MockApplicationController
- Код для изменения MockApplicationController с помощью метода @Before