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"
}
}
Related
I have viewmodel:
#HiltViewModel
class SplashViewMode #Inject constructor(
private val repository: DataStoreRepository,
private val workManager: PeriodicNotificationWorkManager
) : ViewModel() {
init {
workManager.startPeriodicNotifications()
}
}
and a class where I start my periodic work
class PeriodicNotificationWorkManager #Inject constructor(
private val context: Context,
private val workManager: WorkManager
) {
private val WORK_TAG = "my_work"
private val repeatInterval = 15L
private val repeatIntervalTimeUnit: TimeUnit = TimeUnit.MINUTES
fun startPeriodicNotifications() {
val workRequest = PeriodicWorkRequestBuilder<ShowNotificationWorker>(
repeatInterval,
repeatIntervalTimeUnit
)
.addTag(WORK_TAG)
.build()
workManager.enqueueUniquePeriodicWork(
WORK_TAG,
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
}
}
and finally my worker:
#HiltWorker
class ShowNotificationWorker #AssistedInject constructor(
#Assisted val context: Context,
#Assisted val workerParams: WorkerParameters,
//private val evenDao: EventDao
) :
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
NotificationDisplayer(context).showNotification("Test")
return Result.success()
}
}
so far It works fine.
But I need access to EventDao so if I uncomment "private val evenDao: EventDao" in last file I get:
2022-12-31 12:22:03.314 6789-6936/com.rachapps.botanica E/WM-WorkerFactory: Could not instantiate com.rachapps.notification_feature.ShowNotificationWorker
java.lang.NoSuchMethodException: com.rachapps.notification_feature.ShowNotificationWorker.<init> [class android.content.Context, class androidx.work.WorkerParameters]
Solution provided by BrianMwas worked for me finally
https://github.com/google/dagger/issues/2601
#Module
#InstallIn(SingletonComponent::class)
object WorkManagerInitializer: Initializer<WorkManager> {
#Provides
#Singleton
override fun create(#ApplicationContext context: Context): WorkManager {
val entryPoint = EntryPointAccessors.fromApplication(
context,
WorkManagerInitializerEntryPoint::class.java
)
val configuration = Configuration
.Builder()
.setWorkerFactory(entryPoint.hiltWorkerFactory())
.setMinimumLoggingLevel(if (BuildConfig.DEBUG) Log.DEBUG else Log.INFO)
.build()
WorkManager.initialize(context, configuration)
return WorkManager.getInstance(context)
}
override fun dependencies(): MutableList<Class<out Initializer<*>>> {
return mutableListOf()
}
#InstallIn(SingletonComponent::class)
#EntryPoint
interface WorkManagerInitializerEntryPoint {
fun hiltWorkerFactory(): HiltWorkerFactory
}
}
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,
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)
}
}
}
So first of all I created my worker class called UpdaterWorker.
After that I overrided getWorkManagerConfiguration inside my application class.
And I created a WorkerFactory class.
Is that all I have to do, or did I miss something?
How can I create an one time request?
When I try to inject updater worker to main activity, I get this error: Dagger does not support injecting #AssistedInject type, .workmanager.UpdaterWorker. Did you mean to inject its assisted factory type instead?
**Updater worker**
#HiltWorker
class UpdaterWorker #AssistedInject constructor(
private val api: ReservationApi,
private val updaterUrl: String,
private val prefManager: PrefManager,
#Assisted private val context: Context,
#Assisted workerParams: WorkerParameters
) : Worker(context, workerParams) {
...
}
** Worker Factory**
class WorkerFactory #Inject constructor(
private val api: ReservationApi,
#param:Named("update_url") private val updaterUrl: String,
private val prefManager: PrefManager,
) : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters
): ListenableWorker? {
return when (workerClassName) {
WorkerEnums.UpdaterWorker.name -> {
UpdaterWorker(api, updaterUrl, prefManager, appContext, workerParameters)
}
else ->
throw Resources.NotFoundException("Worker not found")
}
}
}
** My application class**
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
}
**Main Activity**
#Inject
lateinit var updaterWorker: UpdaterWorker
A WorkerFactory is not needed and furthermore, you don't need to (and can't) inject the Worker into any other class. Here is the right approach:
Worker
#HiltWorker
class UpdaterWorker #AssistedInject constructor(
private val api: ReservationApi,
private val updaterUrl: String,
private val prefManager: PrefManager,
#Assisted private val context: Context,
#Assisted workerParams: WorkerParameters
) : Worker(context, workerParams) {
...
}
Usage of Worker (e.g here in viewmodel)
class MyTestViewModelForWorker(#ApplicationContext private val context: Context) : ViewModel() {
private val myTestWork = OneTimeWorkRequestBuilder<UpdateWorker>
.build()
fun startManager() {
WorkManager.getInstance(context).enqueue(myTestWork)
}
}
Then, it is important to override the default workerfactory with the hiltfactory in you app class
Overriding default WorkerFactory
#HiltAndroidApp
class App : Application(), Configuration.Provider {
#Inject lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration(): Configuration =
Configuration.Builder().setWorkerFactory(workerFactory).build()
}
And finally, you need to remove the default workmanagerinitalizator inside your manifest:
AppManifest
<provider
android:name="androidx.work.impl.WorkManagerInitializer"
android:authorities="${applicationId}.workmanager-init"
tools:node="remove" />
Next time, please read the documentation of hilt, because everything I wrote here is perfectly explained there and second of all, the approach I am showing here will change with the release of androidx.work:work-runtime-ktx:2.6.0
I was wondering how can I pass application dependency to ViewModel using Hilt?
I was trying with AndroidViewModel, but I couldn't make it. Can someone help me? Some short sample could will mean a lot to me.
This is my ViewModel:
class MainViewModel #ViewModelInject constructor(
private val application: Application,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
This is my hilt module
#Module
#InstallIn(ApplicationComponent::class)
object DatabaseModule {
#Singleton
#Provides
fun provideDatabase(
#ApplicationContext context: Context
) = Room.databaseBuilder(
context,
MyDatabase::class.java,
"my_database"
).build()
#Singleton
#Provides
fun provideDao(database: MyDatabase) = database.myDao()
#Singleton
#Provides
fun provideRepository(myDao: MyDao) = Repository(myDao)
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
}
Everything else is fine, and I got the error message:
Caused by: java.lang.RuntimeException: Cannot create an instance of
class com.example.example.viewmodel.MainViewModel
Caused by: java.lang.InstantiationException:
java.lang.Class<com.example.example.viewmodel.MainViewModel> has
no zero argument constructor
You can see full source https://github.com/Kotlin-Android-Open-Source/MVI-Coroutines-Flow/tree/dagger_hilt
Repository:
#Singleton
class UserRepositoryImpl #Inject constructor(
private val userApiService: UserApiService,
private val dispatchers: CoroutineDispatchers,
...
) : UserRepository { ... }
Usecases:
class AddUserUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke(user: User) = userRepository.add(user)
}
class RemoveUserUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke(user: User) = userRepository.remove(user)
}
class RefreshGetUsersUseCase #Inject constructor(private val userRepository: UserRepository) {
suspend operator fun invoke() = userRepository.refresh()
}
...
ViewModel:
class MainVM #ViewModelInject constructor(
private val getUsersUseCase: GetUsersUseCase,
private val refreshGetUsers: RefreshGetUsersUseCase,
private val removeUser: RemoveUserUseCase,
) : ViewModel() { ... }
Activity:
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), View {
private val mainVM by viewModels<MainVM>()
...
}
Edited:
To inject application context:
First, remove this definition, because Hilt already provides application context:
#Singleton
#Provides
fun provideApplicationContext() = MyApplication()
Second, Use #ApplicationContext annotation on your context parameter.
class MainViewModel #ViewModelInject constructor(
#ApplicationContext private val context: Context,
private val repository: Repository,
#Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
Use #ApplicationContext Context context as a parameter in the constructor.