Я новичок в модульном тестировании в Android и прошел несколько уроков, чтобы познакомиться с mockito и robolectric.
Мое приложение использует Dagger 2 для ввода моего EventService
в мой MainActivity
.Для моего MainActivityUnitTest
я настроил TestServicesModule
для предоставления смоделированной версии EventService
, чтобы я мог использовать Robolectric для запуска модульных тестов против моего MainActivity
У меня проблемаполучение ServiceCallback
на моем EventService.getAllEvents(callback: ServiceCallback)
для выполнения в модульном тесте.В @Setup
моего MainActivityUnitTest
класса я подтвердил, что EventService
вводится как поддельный объект.Я прошел несколько уроков и постов в блоге, и, насколько я могу судить, я все делаю правильно.Функция refreshData()
в MainActivity
успешно вызывается, и я вижу, что выполняется вызов eventsService.getAllEvents(callback)
.Но лямбда-функция doAnswer {}
никогда не выполняется.
Вот мой соответствующий код:
AppComponent.kt
@Singleton
@Component(modules = [
AppModule::class,
ServicesModule::class,
FirebaseModule::class
])
interface AppComponent {
fun inject(target: MainActivity)
}
ServicesModule.kt
@Module
open class ServicesModule {
@Provides
@Singleton
open fun provideEventService(db: FirebaseFirestore): EventsService {
return EventsServiceImpl(db)
}
}
EventsService.kt
interface EventsService {
fun getAllEvents(callback: ServiceCallback<List<Event>>)
fun getEvent(id: String, callback: ServiceCallback<Event?>)
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
@Inject lateinit var eventsService: EventsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
(application as App).appComponent.inject(this)
...
}
override fun onStart() {
super.onStart()
refreshData()
}
eventsService.getAllEvents(object: ServiceCallback<List<Event>> {
override fun onCompletion(result: List<Event>) {
viewModel.allEvents.value = result
loading_progress.hide()
}
})
}
Теперь перейдем к тестам:
TestAppComponent.kt
@Singleton
@Component(modules = [
TestServicesModule::class
])
interface TestAppComponent : AppComponent {
fun inject(target: MainActivityUnitTest)
}
TestServicesModule.kt
@Module
class TestServicesModule {
@Provides
@Singleton
fun provideEventsService(): EventsService {
return mock()
}
}
MainActivityUnitTest.kt
@RunWith(RobolectricTestRunner::class)
@Config(application = TestApp::class)
class MainActivityUnitTest {
@Inject lateinit var eventsService: EventsService
@Before
fun setup() {
val testComponent = DaggerTestAppComponent.builder().build()
testComponent.inject(this)
}
@Test
fun givenActivityStarted_whenLoadFailed_shouldDisplayNoEventsMessage() {
val events = ArrayList<Event>()
doAnswer {
//this block is never hit during debug
val callback: ServiceCallback<List<Event>> = it.getArgument(0)
callback.onCompletion(events)
}.whenever(eventsService).getAllEvents(any())
val activity = Robolectric.buildActivity(MainActivity::class.java).create().start().visible().get()
val noEventsView = activity.findViewById(R.id.no_events) as View
//this always evaluates to null because the callback is never set from the doAnswer lambda
assertThat(callback).isNotNull()
verify(callback)!!.onCompletion(events)
assertThat(noEventsView.visibility).isEqualTo(View.VISIBLE)
}
}
Редактировать: добавление приложения и TestApp
open class App : Application() {
private val TAG = this::class.qualifiedName
lateinit var appComponent: AppComponent
override fun onCreate() {
super.onCreate()
appComponent = initDagger(this)
}
open fun initDagger(app: App): AppComponent {
return DaggerAppComponent.builder().appModule(AppModule(app)).build()
}
}
class TestApp : App() {
override fun initDagger(app: App): AppComponent {
return DaggerTestAppComponent.builder().build()
}
}