Instrumented test - android

I am trying to run a test:
#HiltAndroidTest
class ActionDaoTest {
#get : Rule
var hiltRule = HiltAndroidRule(this)
#get : Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Inject
#Named("test_db")
lateinit var database: MyDatabase
private lateinit var actionDao: ActionDao
#Before
fun setup() {
hiltRule.inject()
actionDao = database.actionDao()
}
#After
fun teardown(){
database.close()
}
#Test
fun insert_assetTrue() = runTest{
val action = ActionEntity("name","description", LocalDate.now())
actionDao.insert(action)
val actionList= actionDao.selectAll().first()
assertThat(actionList).contains(action)
}
}
I get an error:
java.lang.NoSuchMethodError: No virtual method find(Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/internal/ThreadSafeHeapNode; in class Lkotlinx/coroutines/internal/ThreadSafeHeap; or its super classes

I downgraded
'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'
to
'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0'
and now it works...

The find method is defined in the coroutine-core module. In my case, I missed to add that module in my dependencies. As soon as I set it up like this, it worked:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4'

Related

Hilt - Unit Tests

I would like to start using Hilt in my test.
Gradle:
android{
...
defaultConfig{
...
testInstrumentationRunner "com.rachapps.myfitapp.HiltTestRunner"
}
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.28-alpha'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.38.1'
...
class HiltTestRunner : AndroidJUnitRunner() {
override fun newApplication(
cl: ClassLoader?,
className: String?,
context: Context?
): Application {
return super.newApplication(cl, HiltTestApplication::class.java.name, context)
}
}
test class:
#SmallTest
#HiltAndroidTest
class BodyPartDaoTest {
#get : Rule
var hiltRule = HiltAndroidRule(this)
#get : Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
#Inject
#Named("test_db")
lateinit var database: MyFitDatabase
private lateinit var bodyPartDao: BodyPartDao
private lateinit var exerciseDao: ExerciseDao
#Before
fun setup() {
hiltRule.inject()
bodyPartDao = database.bodyPartDao()
exerciseDao = database.exerciseDao()
}
...
}
Module:
#Module
#InstallIn(SingletonComponent::class)
object TestAppModule {
#Provides
#Named("test_db")
fun provideInMemoryDb(#ApplicationContext context: Context) : MyFitDatabase {
return Room.inMemoryDatabaseBuilder(context, MyFitDatabase::class.java).allowMainThreadQueries().build()
}
}
While executing test I receive an error:
C:\Radek\Android\MyFitApp\app\build\generated\source\kapt\debugAndroidTest\com\rachapps\myfitapp\data\dao\BodyPartDaoTest_TestComponentDataSupplier.java:14: error: BodyPartDaoTest_TestComponentDataSupplier is not abstract and does not override abstract method get() in TestComponentDataSupplier
public final class BodyPartDaoTest_TestComponentDataSupplier extends TestComponentDataSupplier {
^
C:\Radek\Android\MyFitApp\app\build\generated\source\kapt\debugAndroidTest\com\rachapps\myfitapp\data\dao\BodyPartDaoTest_TestComponentDataSupplier.java:15: error: get() in BodyPartDaoTest_TestComponentDataSupplier cannot override get() in TestComponentDataSupplier
protected TestComponentData get() {
^
return type TestComponentData is not compatible with Map<Class<?>,TestComponentData>
2 errors
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:compileDebugAndroidTestJavaWithJavac'.
I am not sure as to what is the reason for the exception, but the way you are providing in-memory DB to the test, is not what the documentation says you should do.
Try replacing the binding, and see what happens.
Try with keeping Hilt library versions same
//Hilt testing
androidTestImplementation 'com.google.dagger:hilt-android-testing:2.37'
kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.37'
//hilt dependency
kapt "com.google.dagger:hilt-compiler:2.37"
implementation("com.google.dagger:hilt-android:2.37")
I had that problem because of outdated library: com.google.dagger:hilt-android-testing

How to test getter, setter method of a LiveData Android?

I am using Hilt for DI in my project. I am trying write unit test cases for LiveData object, but it's not coming under coverage.
ViewModel
#HiltViewModel
class HealthDiagnosticsViewModel #Inject constructor(
private var networkHelper: NetworkHelper
) : ViewModel() {
var result = MutableLiveData<Int>()
.....
}
My unit test class is as below:
HealthViewModelTest
#HiltAndroidTest
#RunWith(RobolectricTestRunner::class)
#Config(application = HiltTestApplication::class)
class HealthDiagnosticsViewModelTest{
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Inject
lateinit var networkHelper: NetworkHelper
lateinit var healthDiagnosticsViewModel: HealthDiagnosticsViewModel
#Before
fun setUp() {
hiltRule.inject()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
#Test
fun testGetResult() {
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
#Test
fun testSetResult() {
healthDiagnosticsViewModel.result.value = 1
Assert.assertEquals(1, healthDiagnosticsViewModel.result.value)
}
}
Test Cases are passed but it's not coming under method coverage.
I'll share with you the an example of my code that would solve your problem.
I'm usnig ViewModel with Dagger Hilt
You don't have to use Robelectric, you can use MockK library.
Replace your HiltRule with this Rule:
#get:Rule
var rule: TestRule = InstantTaskExecutorRule()
This is my ViewModel class
using MockK, you can mock the networkHelper class without Hilt.
So, your setup method will be like that:
lateinit var networkHelper: NetworkHelper
......
......
......
#Before
fun setUp() {
networkHelper = mockk<NetworkHelper>()
healthDiagnosticsViewModel = HealthDiagnosticsViewModel(networkHelper)
}
4)The most important part in your test is to Observe to the LiveData first.
#Test
fun testGetResult() {
healthDiagnosticsViewModel.result.observeForever {}
val result = healthDiagnosticsViewModel.result.value
Assert.assertEquals(null, result)
}
You can observe to the livedata for each unit test, but keep in mind to Observe first before change data.

ViewModel unit test - Using delays

I am new to testing and I am writing a test for my ViewModel. I have a couple of questions about it.
In my Test method, unless I add a delay(10), the test fails saying:
Wanted but not invoked:
breweryRepository.getBeerStyles();
-> at com.helenc.test.repositories.BreweryRepository.getBeerStyles(BreweryRepository.kt:12)
Actually, there were zero interactions with this mock.
Why do I need a delay? Is there a better way to do it?
I added the test in the test folder (not in androidTest). Is this correct? I am not sure since I am using some androidx dependencies, like androidx.lifecycle.Observer or androidx.arch.core.executor.testing.InstantTaskExecutorRule.
This is my ViewModel test file:
#RunWith(JUnit4::class)
class BeerStylesViewModelTest {
private lateinit var viewModel: BeerStylesViewModel
private lateinit var repository: BreweryRepository
private lateinit var beerStylesObserver: Observer<Resource<List<StylesData>>>
private val successResource = Resource.success(mockedBeerStylesList)
#Rule
#JvmField
val instantExecutorRule = InstantTaskExecutorRule()
#ObsoleteCoroutinesApi
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
#ObsoleteCoroutinesApi
#ExperimentalCoroutinesApi
#Before
fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
repository = mock()
runBlocking {
whenever(repository.getBeerStyles()).thenReturn(successResource)
}
viewModel = BeerStylesViewModel(repository)
beerStylesObserver = mock()
}
#Test
fun `when load beerStyles success`() = runBlocking {
viewModel.beerStyles.observeForever(beerStylesObserver)
delay(10)
verify(repository).getBeerStyles()
verify(beerStylesObserver).onChanged(Resource.loading(null))
verify(beerStylesObserver).onChanged(successResource)
}
#ObsoleteCoroutinesApi
#ExperimentalCoroutinesApi
#After
fun tearDown() {
Dispatchers.resetMain()
mainThreadSurrogate.close()
}
}

Hilt Instrumentation test with Workmanager not working

When I try to run an ActivityScenario in my application that contains a WorkManager I get the following error on start:
java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
Using the WorkManagerTestInitHelper from the work-test artifact doesnt help either.
The WorkManager is defined like this:
#Provides
#Singleton
fun provideWorkmanager(#ApplicationContext context: Context) = WorkManager.getInstance(context)
This is my test atm:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class LoginTest {
#get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
val activityRule = ActivityScenarioRule(MainActivity::class.java)
#Before
fun before() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
}
#Test
fun test() {
...
}
}
This is because #get:Rule executes before the #Before does, as per the Google Documentation:
This rule provides functional testing of a single activity. The activity under test is launched before each test annotated with #Test and before any method annotated with #Before. It's terminated after the test is completed and all methods annotated with #After are finished. To access the activity under test in your test logic, provide a callback runnable to ActivityScenarioRule.getScenario().onActivity().
In order to fix this, you would need to initialise the WorkManager in the test with WorkManagerTestInitHelper before you try to launch the activity.
To do this, you should avoid using ActivityScenarioRule and use ActivityScenario instead, you can do something like this:
#HiltAndroidTest
#RunWith(AndroidJUnit4::class)
class LoginTest {
private lateinit var scenario: ActivityScenario<MainActivity>
#get:Rule
var hiltRule = HiltAndroidRule(this)
#Before
fun before() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
scenario = launchActivity()
}
#Test
fun test() {
scenario.moveToState(Lifecycle.State.CREATED).onActivity {
activity -> // do some test with the activity
}
}
}
To take advantage of ActivitScenarioRule and ensure that the WorkManager is initialized first I created a custom JUnit rule.
class WorkManagerRule : TestRule {
override fun apply(base: Statement?, description: Description?): Statement {
return object : Statement() {
override fun evaluate() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
WorkManagerTestInitHelper.initializeTestWorkManager(context, config)
try {
base?.evaluate()
} finally {
Log.d("WorkManagerRule", "Do some teardown")
}
}
}
}
#RunWith(AndroidJUnit4::class)
#HiltAndroidTest
class MyTest {
#get:Rule(order = 0)
var hiltRule = HiltAndroidRule(this)
#get:Rule(order = 1)
var workMgrRule = WorkManagerRule()
#get:Rule(order = 2)
var activityRule = ActivityScenarioRule(MainActivity::class.java)
#Before
fun init() {
hiltRule.inject()
}
//Your test code here...
}
I used the instructions for creating JUnit rules found here.

Mockito does'nt mock repository

i'm try to test my ViewModel with mockito.
This is my TestClass:
#RunWith(JUnit4::class)
class RatesViewModelTest {
#Rule #JvmField
open val instantExecutorRule = InstantTaskExecutorRule()
#Mock
var observer: Observer<Pair<ArrayList<CurrencyExchangerModel>,Boolean>>? = null
#Mock
private lateinit var repository: RatesRepository
private var currencyList = ArrayList<CurrencyModel>()
private lateinit var viewModel : RatesViewModel
#Before
fun setUp(){
MockitoAnnotations.initMocks(this)
currencyList.add(CurrencyModel("BASE"))
viewModel = RatesViewModel(repository!!)
viewModel.getCurrencyExchangerObservableList().observeForever(observer!!)
}
#Test
fun testNull(){
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
assertTrue(viewModel.getCurrencyExchangerObservableList().hasObservers())
}
}
When this method is invoked:
Mockito.`when`(repository.getFlowableRates()).thenReturn( Flowable.just(currencyList) )
I got this error:
kotlin.UninitializedPropertyAccessException: lateinit property db has
not been initialized
Here the repository:
open class RatesRepository(context:Context) : BaseRepository(context){
#Inject
lateinit var ratesAPI: RatesAPI
#Inject
lateinit var db: Database
/**
* load the updated chatList from API
*/
fun loadCurrencyRatesFromAPI(): Single<ArrayList<CurrencyModel>> {
val supportedCurrency = context.resources.getStringArray(R.array.currencies)
return ratesAPI.getLatestRates(EUR_CURRENCY_ID).map { RatesConverter.getRatesListFromDTO(it,supportedCurrency) }
}
/**
* save rates on DB
*/
fun saveCurrencyRatesOnDB(list:ArrayList<CurrencyModel>): Completable {
return db.currencyRatesDAO().insertAll(list)
}
/**
* get flawable rates from DB
*/
fun getFlowableRates(): Flowable<List<CurrencyModel>> {
return db.currencyRatesDAO().getAll()
}
companion object {
const val EUR_CURRENCY_ID = "EUR" //BASE
}
}
What i'm doing wrong ?
Thx !
When you define behaviour of a mock and use the when(...).then(...) notation of mockito,
the method itself is invoked (by mockito, normally not relevant for your test).
In your case that is a problem because db is not initialized.
To avoid this issues use the doReturn(...).when(...) syntax in these cases,
which does not cause the method invocation.
Mockito.doReturn(Flowable.just(currencyList)).when(repository).getFlowableRates();
(You might need to adjust this java syntax to make it kotlin compatible)

Categories

Resources