I have following android test class
#HiltAndroidTest
#UninstallModules(PreferencesModule::class, CacheModule::class)
class PlayerRepositoryTest {
private val fakeServer = FakeServer()
private lateinit var repository: PlayerRepository
private lateinit var api: GameApi
private lateinit var cache: Cache
#get: Rule
var hiltRule = HiltAndroidRule(this)
#get: Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Inject
lateinit var database: GameDatabase
#Inject
lateinit var retrofitBuilder: Retrofit.Builder
#Inject
lateinit var apiPlayerMapper: ApiPlayerDetailsMapper
#Inject
lateinit var apiPaginationMapper: ApiPaginationMapper
#BindValue
#JvmField
val preferences: Preferences = FakePreferences()
#Module
#InstallIn(SingletonComponent::class)
object TestCacheModule {
#Provides
fun provideRoomDatabase(): GameDatabase {
return Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
GameDatabase::class.java
)
.allowMainThreadQueries()
.build()
}
}
#Before
fun setup(){
fakeServer.start()
preferences.deleteTokenInfo()
preferences.putToken("validToken")
preferences.putTokenExpirationTime(Instant.now().plusSeconds(3600).epochSecond)
preferences.putTokenType("Bearer")
hiltRule.inject()
api = retrofitBuilder
.baseUrl(fakeServer.baseEndpoint)
.build()
.create(GameApi::class.java)
cache = RoomCache(database.sportDao(), database.playerDao())
repository = PlayerRepositoryIml(
api,
cache,
apiPlayerMapper,
apiPaginationMapper
)
}
#Test
fun requestMorePlayers_success() = runBlocking{
//Given
val expectedAnimalId =124L
fakeServer.setHappyPathDispatcher()
//When
val paginatedPlayers = repository.requestMorePlayers(1, 100)
//Then
val player = paginatedPlayers.players.first()
assertThat(player.id).isEqualTo(expectedAnimalId)
}
#After
fun tearDown(){
fakeServer.shutdown()
}
}
When I run this test file I get the following error:
D:\android projects\cricket\app\build\generated\hilt\component_sources\debugAndroidTest\common\data\PlayerRepositoryTest_TestComponentDataSupplier.java:8: error: cannot find symbol
import dagger.hilt.android.internal.testing.root.DaggerPlayerRepositoryTest_HiltComponents_SingletonC;
symbol: class DaggerPlayerRepositoryTest_HiltComponents_SingletonC
location: package dagger.hilt.android.internal.testing.root
D:\android projects\generated\hilt\component_sources\debugAndroidTest\dagger\hilt\android\internal\testing\root\PlayerRepositoryTest_HiltComponents.java:137:
error: [Dagger/MissingBinding] sourabh.pal.cricket.common.data.cache.Cache cannot be provided without an #Provides-annotated method.
public abstract static class SingletonC implements FragmentGetContextFix.FragmentGetContextFixEntryPoint,
Related
I am trying to test DataSource with Proto DataStore.
class PreferenceDataSource #Inject constructor(
private val userPreferences: DataStore<UserPreferences>
)
So, I am implementing like this:
#RunWith(RobolectricTestRunner::class)
#ExperimentalCoroutinesApi
class PreferenceDataSourceTest {
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
private lateinit var prefDataSource: PreferenceDataSource
#Before
fun setUp(){
Dispatchers.setMain(Dispatchers.Unconfined)
MockKAnnotations.init(this, relaxed = true)
val datastore = DataStore<UserPreferences>() // this doesn't work.
prefDataSource = PreferenceDataSource(userPreferences = datastore)
}
#Test
fun test() {
}
}
How can I test with Proto DataStore? I mean, I don't want to fake it but want to see if it really updates or deletes correctly as well.
Here's my solution with Robolectric.
#ExperimentalCoroutinesApi
#RunWith(RobolectricTestRunner::class)
class PreferenceDataSourceTest {
#get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private lateinit var dataSource: PreferenceDataSource
private lateinit var dataStore: DataStore<UserPreferences>
#Before
fun setUp() {
Dispatchers.setMain(Dispatchers.Unconfined)
dataStore = DataStoreFactory.create(
serializer = UserPreferencesSerializer(),
scope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob()),
) {
context.dataStoreFile("test_user_preferences.pb")
}
dataSource = PreferenceDataSource(userPreferences = dataStore)
}
}
I am trying to test Worker but i can't inject it
Error while gradle building:
Dagger does not support injecting #AssistedInject type,
com.hussien.quoty.sync.NotificationWorker. Did you mean to inject its assisted factory type instead?
public com.hussien.quoty.sync.NotificationWorker notificationWorker;
Test Class
#ExperimentalCoroutinesApi
#MediumTest
#RunWith(AndroidJUnit4::class)
#HiltAndroidTest
class NotificationWorkerTest {
private lateinit var notificationManager: NotificationManager
#get:Rule
var hiltRule = HiltAndroidRule(this)
//NOTE: HERE AM TRYING TO INJECT IT
#Inject
lateinit var notificationWorker: NotificationWorker
#Before
fun setUp() {
hiltRule.inject()
val context = ApplicationProvider.getApplicationContext<Context>()
notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
#Test
fun doWork() = runTest {
notificationWorker.doWork()
assertThat(
notificationManager.activeNotifications.map { it.id },
hasItem(NotificationUtils.QUOTES_NOTIFICATION_ID)
)
}
}
Worker Class
#HiltWorker
class NotificationWorker #AssistedInject constructor(
#Assisted context: Context,
#Assisted workerParams: WorkerParameters,
private val repository: QuotesRepository,
private val settingsDataStoreManager: SettingsDataStoreManager,
private val notificationUtils: NotificationUtils,
): CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val settings = settingsDataStoreManager.settingsPreferencesFlow.first()
val randomQuote = repository.getRandomQuote(settings.tags, settings.languages)
notificationUtils.sendQuoteNotification(randomQuote)
return Result.success()
}
companion object {
const val TAG = "notification_worker"
}
}
Hi I am trying to test livedata which is in my viewmodel, I have written to test to verify that the repository call has been made
how can I check if the live data object in this case, and what additional test cases I should be writing for this code in viewModel
Thanks
R
MainActivityViewModel
class MainActivityViewModel #Inject constructor(
private val dataRepository: DataRepository
): ViewModel() {
val _charactersLiveData = MutableLiveData<Result<ArrayList<Character>>>()
val charactersLiveData: LiveData<Result<ArrayList<Character>>> = _charactersLiveData
fun fetchCharacters() {
viewModelScope.launch {
_charactersLiveData.value = dataRepository.getCharacters()
}
}
}
MainActivityViewModelTest
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#get:Rule
val coroutineRule = MainCoroutineRule()
#Mock
private lateinit var dataRepository: DataRepository
#InjectMocks
private lateinit var mainActivityViewModel: MainActivityViewModel
#Test
fun fetchCharacters() {
runBlockingTest {
mainActivityViewModel.fetchCharacters()
verify(dataRepository).getCharacters()
}
}
}
You can reference this article: https://medium.com/androiddevelopers/unit-testing-livedata-and-other-common-observability-problems-bb477262eb04
The main idea here is:
Mock dataRepository
When dataRepository calls getCharacters(), it should return the testing value. E.g. listOf('a')
Execute mainActivityViewModel.fetchCharacters()
Expect the value of your live data
assertEquals(mainActivityViewModel.charactersLiveData.getOrAwaitValue(), listOf('a'))
Try this if any issues then let me know. I have try to do according to your class and data but if you need then can changes.
#RunWith(MockitoJUnitRunner::class)
class MainActivityViewModelTest {
#get:Rule
val coroutineRule = MainCoroutineRule()
#Mock
private lateinit var dataRepository: DataRepository
#InjectMocks
private lateinit var mainActivityViewModel: MainActivityViewModel
#Mock
private lateinit var dataObserver: Observer<Result<List<Character>>>
#Test
fun fetchCharacters() {
runBlockingTest {
Mockito.`when`(datarepository).getcharacter()
.thenreturn(Result.success(listOf(Character (
Name=A,
Type=hero))))
mainActivityViewModel.fetchCharacters()
mainActivityViewModel.charactersLiveData.observeForever(dataObserver)
Mockito.verify(dataRepository).getCharacters()
Mockito.verify(dataObserver).onChanged(Result.success(listOf(Character (
Name=A,
Type=hero))))
mainActivityViewModel.charactersLiveData.removeObserver(dataObserver)
}
}
}
I started to implement automated tests recently and I have a little doubt of how to test my ViewModel functions. My project follows some clean architecture concepts, I'm using lib kotlin for mockito (https://github.com/nhaarman/mockito-kotlin) and I would like to do the following test: When call makeLoginUser get success then set loginUserLiveData .
I have studied some concepts and I know the tests I should do, but I am still in doubt as to how to do them, which objects should I mock for a given test and which should be instantiated. I needed a small example to address me.
Here is my test class and some classes of the structure of my project.
TestClass
#RunWith(JUnit4::class)
class MainViewModelTest {
#get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Mock
private lateinit var myRepository: MyRepository
private lateinit var loginUserUseCase: LoginUserUseCase
private lateinit var checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase
private lateinit var logoutUserUseCase: LogoutUserUseCase
private lateinit var mainViewModel: MainViewModel
#Before
fun setUp() {
MockitoAnnotations.initMocks(this)
}
#Test
fun `When call makeLoginUser get success then set loginUserLiveData`() {
// prepare
myRepository = mock { // should I mock or instantiate?
on { execLoginUser(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) } doReturn Flowable.just(true)
}
loginUserUseCase = mock { // should I mock or instantiate?
on { execute(ArgumentMatchers.anyMap()) } doReturn Flowable.just(true)
}
mainViewModel = MainViewModel(loginUserUseCase, checkUserAuthenticatedUsecase, logoutUserUseCase)
val observer = mock<Observer<ResultState<Boolean>>> { mock }
// Execute
mainViewModel.makeLoginUser("test#gmail.com", "123456")
// Check
// ?
}
}
ViewModel
// components 'LoginUserUseCase', 'CheckUserAuthenticatedUsecase' and 'LogoutUserUseCase' injected via koin
class MainViewModel(
private val loginUserUseCase: LoginUserUseCase,
private val checkUserAuthenticatedUsecase: CheckUserAuthenticatedUsecase,
private val logoutUserUsecase: LogoutUserUseCase
): BaseViewModel() {
val loginUserLiveData = MutableLiveData<ResultState<Boolean>>()
val userAuthenticatedLiveData = MutableLiveData<ResultState<Boolean>>()
val logoutUserLiveData = MutableLiveData<ResultState<Boolean>>()
fun makeLoginUser(email: String, password: String) {
loginUserLiveData.postValue(ResultState.Loading())
loginUserUseCase.execute(mapOf(EMAIL to email, PASSWORD to password))
.subscribe({
loginUserLiveData.postValue(ResultState.Success(it))
}, {
loginUserLiveData.postValue(ResultState.Error(it))
}).addTo(disposables)
}
...
}
UseCase Domain Class
// components 'ThreadExecutor' and 'PostExecutionThread' injected via koin
abstract class FlowableUseCase<T, in Params> constructor(
private val threadExecutor: ThreadExecutor,
private val postExecutionThread: PostExecutionThread) {
protected abstract fun buildUseCaseObservable(params: Params? = null): Flowable<T>
open fun execute(params: Params? = null): Flowable<T> {
return this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.scheduler)
}
}
Abstraction UseCases
// components 'MyRepository', 'ThreadExecutor' and 'PostExecutionThread' injected via koin
// 'MyRepository' abstracts remote and local data repository
class LoginUserUseCase(
private val myRepository: MyRepository,
threadExecutor: ThreadExecutor,
postExecutionThread: PostExecutionThread
): FlowableUseCase<Boolean, Map<String, Any>?>(threadExecutor, postExecutionThread) {
override fun buildUseCaseObservable(params: Map<String, Any>?) = myRepository
.execLoginUser(
params?.get(EMAIL) as String,
params[PASSWORD] as String
)
}
i have this test class running with MockitoJUnitRunner before and then i added RoboLectric, now i use RobolectricTestRunner
So the first thing it tryed is running my old tests but just changing the runner and the test now always fail. I do not really understand what is happening here, i am just trying to make my old test work with RobolectricTestRunner without stop using Mockito.
My Code before changing to RoboLectric (TEST PASS SUCCESSFULLY)
#RunWith(MockitoJUnitRunner::class)
class LauncherViewModelTest {
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#Mock
private lateinit var mockContext: MyApplication
#Mock
private lateinit var mockedDatabase: MyDatabase
#Mock
private lateinit var session: Session
//
#Mock
lateinit var mockedDatabaseRxWrapper: DatabaseRxWrapper
/** Evaluated class **/
#InjectMocks
lateinit var launcherViewModel: LauncherViewModel
#Test
fun checkIfHasSessionSuccess() {
val sessionFlowable: Flowable<Session> = Flowable.fromCallable { session }
FieldSetter.setField(launcherViewModel,
launcherViewModel.javaClass.getDeclaredField("databaseRxWrapper"), mockedDatabaseRxWrapper)
doReturn(sessionFlowable).`when`(mockedDatabaseRxWrapper).getSession()
launcherViewModel.checkIfHasSession()
//$hasSession is a mutable live data
Assert.assertEquals(true, launcherViewModel.hasSession.value)
}
}
My Code after changing to RoboLectric : (DatabaseRxWrapper.getSession() returns always null even when i use Mockito.doReturn().when())
#RunWith(RobolectricTestRunner::class)
class LauncherViewModelTest {
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}
#Rule
#JvmField
val rule = InstantTaskExecutorRule()
#get:Rule
val mockitoRule = MockitoJUnit.rule()
#Mock
private lateinit var mockContext: MyApplication
#Mock
private lateinit var mockedDatabase: MyDatabase
#Mock
private lateinit var session: Session
//
#Mock
lateinit var mockedDatabaseRxWrapper: DatabaseRxWrapper
/** Evaluated class **/
#InjectMocks
lateinit var launcherViewModel: LauncherViewModel
#Test
fun checkIfHasSessionSuccess() {
val sessionFlowable: Flowable<Session> = Flowable.fromCallable { session }
FieldSetter.setField(launcherViewModel,
launcherViewModel.javaClass.getDeclaredField("databaseRxWrapper"), mockedDatabaseRxWrapper)
doReturn(sessionFlowable).`when`(mockedDatabaseRxWrapper).getSession()
launcherViewModel.checkIfHasSession()
//$hasSession is a mutable live data
Assert.assertEquals(true, launcherViewModel.hasSession.value)
}
}
Class under Test
class LauncherViewModel(application: Application) : AndroidViewModel(application) {
#Inject
lateinit var databaseRxWrapper: DatabaseRxWrapper
val hasSession: MutableLiveData<Boolean> = MutableLiveData()
val application by lazy { getApplication<MyApplication>() }
init {
application.getAppComponent()?.inject(this)
}
fun saveLocation(location: Location) = sharedPreferenceManager.saveLocation(location)
fun checkIfHasSession() {
databaseRxWrapper.getSession()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
hasSession.postValue(true)
}, {
hasSession.postValue(false)
})
}
}
I found that you don't need the rule for rewriting the configuration of RX if you are running with Robolectric
So the code worked after deleting this classrule:
companion object {
#ClassRule
#JvmField
val schedulers = RxImmediateeSchedulerRule()
}