未在 Android 单元测试中使用注入的模拟对象执行回调
Callback not executed in Android unit test with injected mock object
我是 Android 单元测试的新手,并且已经学习了几个教程来熟悉 mockito 和 robolectric。
我的应用程序使用 Dagger 2 将我的 EventService
注入我的 MainActivity
。对于我的 MainActivityUnitTest
,我设置了一个 TestServicesModule
来提供 EventService
的模拟版本,这样我就可以使用 Robolectric 到 运行 单元测试来对付我的 MainActivity
我在让 EventService.getAllEvents(callback: ServiceCallback)
上的 ServiceCallback
在单元测试中执行时遇到问题。我已经在 MainActivityUnitTest
class 的 @Setup
中验证了 EventService
是作为模拟对象注入的。我已经浏览了几篇教程和博客文章,据我所知,我做的一切都是正确的。 MainActivity
中的 refreshData()
函数被成功调用,我可以看到正在执行对 eventsService.getAllEvents(callback)
的调用。但是 doAnswer {}
lambda 函数永远不会被执行。
这是我的相关代码:
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()
}
})
}
现在我们进入测试:
测试AppComponent.kt
@Singleton
@Component(modules = [
TestServicesModule::class
])
interface TestAppComponent : AppComponent {
fun inject(target: MainActivityUnitTest)
}
测试ServicesModule.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)
}
}
编辑:添加 App 和 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()
}
}
看起来您正在使用不同的组件来注入您的测试和 activity。由于它们是不同的组件,我怀疑您正在使用 2 个不同的 eventsService 实例。
您的测试使用本地 DaggerTestAppComponent。
@Inject lateinit var eventsService: EventsService
@Before
fun setup() {
val testComponent = DaggerTestAppComponent.builder().build()
testComponent.inject(this)
}
虽然您的 Activity 使用应用程序中的 appComponent。
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)
...
}
为了克服这个问题,您可以考虑添加应用程序的测试版本 class,这将允许您用 TestAppComponent 替换应用程序中的 AppComponent。 Robolectric 应该允许您创建一个测试应用程序,如下所示:http://robolectric.org/custom-test-runner/
我是 Android 单元测试的新手,并且已经学习了几个教程来熟悉 mockito 和 robolectric。
我的应用程序使用 Dagger 2 将我的 EventService
注入我的 MainActivity
。对于我的 MainActivityUnitTest
,我设置了一个 TestServicesModule
来提供 EventService
的模拟版本,这样我就可以使用 Robolectric 到 运行 单元测试来对付我的 MainActivity
我在让 EventService.getAllEvents(callback: ServiceCallback)
上的 ServiceCallback
在单元测试中执行时遇到问题。我已经在 MainActivityUnitTest
class 的 @Setup
中验证了 EventService
是作为模拟对象注入的。我已经浏览了几篇教程和博客文章,据我所知,我做的一切都是正确的。 MainActivity
中的 refreshData()
函数被成功调用,我可以看到正在执行对 eventsService.getAllEvents(callback)
的调用。但是 doAnswer {}
lambda 函数永远不会被执行。
这是我的相关代码:
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()
}
})
}
现在我们进入测试:
测试AppComponent.kt
@Singleton
@Component(modules = [
TestServicesModule::class
])
interface TestAppComponent : AppComponent {
fun inject(target: MainActivityUnitTest)
}
测试ServicesModule.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)
}
}
编辑:添加 App 和 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()
}
}
看起来您正在使用不同的组件来注入您的测试和 activity。由于它们是不同的组件,我怀疑您正在使用 2 个不同的 eventsService 实例。
您的测试使用本地 DaggerTestAppComponent。
@Inject lateinit var eventsService: EventsService
@Before
fun setup() {
val testComponent = DaggerTestAppComponent.builder().build()
testComponent.inject(this)
}
虽然您的 Activity 使用应用程序中的 appComponent。
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)
...
}
为了克服这个问题,您可以考虑添加应用程序的测试版本 class,这将允许您用 TestAppComponent 替换应用程序中的 AppComponent。 Robolectric 应该允许您创建一个测试应用程序,如下所示:http://robolectric.org/custom-test-runner/