未在 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/