Обратный вызов не выполняется в модульном тесте Android с введенным фиктивным объектом - PullRequest
0 голосов
/ 13 сентября 2018

Я новичок в модульном тестировании в 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()
    }
}

1 Ответ

0 голосов
/ 14 сентября 2018

Похоже, вы используете другой компонент, чтобы ввести свой тест и активность.Поскольку они являются разными компонентами, я подозреваю, что вы используете 2 разных экземпляра eventsService.

В вашем тесте используется локальный DaggerTestAppComponent.

@Inject lateinit var eventsService: EventsService

@Before
fun setup() {
    val testComponent = DaggerTestAppComponent.builder().build()
    testComponent.inject(this)
}

В то время как ваша активность использует 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)
        ...
    }

Чтобы преодолеть это, вы можете подумать о добавлении тестовой версии вашего класса приложений, это позволит вам заменить AppComponent в вашем приложении на TestAppComponent.Robolectric должен позволить вам создать тестовое приложение следующим образом: http://robolectric.org/custom-test-runner/

...