What is the difference between androidx.collection.ArrayMap vs android.util.ArrayMap? - android

I already looked at the source code, the only thing i'm curious about to know why both the versions are exist one should had to be deprecated, no ?
Today I was writing unit tests for one of my viewModel class, in the repository class I have used android.util.ArrayMap. I did initialize ArrayMap with fake value but while debugging found that arrayMap size is always zero.
arrayMap["key1"] = someValue
arrayMap["key2"] = someValue
arrayMap["key3"] = someValue
But when I change it to androidx.collection.ArrayMap it works fine. But the problem is I already used android.util.ArrayMap in my repository and viewModel class and at this moment I don't want to convert that to androidx.collection.ArrayMap.
Here is the test class.
import android.util.ArrayMap
//import androidx.collection.ArrayMap // switching to this works fine
import org.junit.*
import org.junit.Assert.assertNotNull
import org.junit.runners.MethodSorters
#FixMethodOrder(MethodSorters.NAME_ASCENDING)
class AudioViewModelTest {
private lateinit var audioMap: ArrayMap<Int, AudioItem>
#Before
fun setUp() {
audioMap = ArrayMap()
val audioItem = AudioItem()
audioMap[1] = audioItem
audioMap[2] = audioItem
audioMap[3] = audioItem
assertNotNull(audioMap[1])// this is failing
}
#Test
fun testAudioMap() {
assertNotNull(audioMap[1])// this is failing
}
}
Can someone please explain why android.util.ArrayMap always have size as ZERO even if I add some element into that.

You cannot use Android platform classes in your local tests. In your local test, you have just mock of the android.util.ArrayMap class. That's why it's size is always 0. You have to use instrumented tests or use androidx.collection.ArrayMap

Related

How to unit test a read-only SharedFlow properly?

I'm struggling with mine SharedFlow inside of the repo. So, in the real implementation I've got a singleton class that subscribes to different flows, makes some operations and then does shareIn() to all other places in the app as a single source of truth (service, VMs and so on). So it's a huge long chain of calls and setups.
When I'm trying to get a first emitted value, I'm either getting a non-completed coroutine issue (all other tests work fine with my test dispatcher, so I don't think that it's a dispatcher issue), or when I'm trying to use sharedFlow.toList(someMutableList) as I found in other examples, I'm getting an empty list of results. I cannot emit to the flow from my test because 1) I want to test that it works well by itself (that it combines other flows, makes some api calls etc) 2) and it's obviously a read-only class field and I want it to be so.
Here I created a really simple version of my repo and test class and a basic vision what I want to achieve.
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.runTest
import org.junit.Test
class Repo(dispatcher: CoroutineDispatcher) {
val flow1 = MutableStateFlow(0)
val flow2 = MutableStateFlow(1)
val sharedFlow = flow1.flatMapLatest { zero ->
flow2.map { one -> zero to one }
}.transformLatest { (zero, one) ->
emit(zero + one)
}.shareIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, 1)
}
class TestClass {
#Test
fun `checking the right state for the shared flow in repo`() = runTest {
val repo = Repo(testDispatcher)
launch {
val state = repo.sharedFlow.firstOrNull()
assertEquals(state, 1) // 0 + 1
}.join()
}
}
What am I doing wrong here? Thanks in advance for any help!
Okay, so I've figured it out. The funniest and most awkward thing is that my example works perfectly, but the real implementation didn't.
The issue was with #RelaxedMockK that swallowed the exception that one of my flows was not mocked properly. Because of that the coroutine wasn't completed and caused a time out exception instead of a real-swallowed one (that's why I thought that maybe I had an issue with a dispatcher). So, if you use the test dispatcher, provide it to your repo, mock everything properly without relying on relaxed mockks when it's not a unit-result function and wait until the flow receives the first data, then everything should work well.

Android SparseArray is null when running a Unit Test

I have a unit test for a kotlin object which uses a SparseArray.
The test always failed because the SparseArray is always null despite it's initialization.
object Exam : KoinComponent {
var map = SparseArray<Char?>()
init {
map.put(0, 'a')
map.put(1, 'b')
map.put(2, 'c')
map.put(3, 'd')
}
fun getChar(key: Int): Char? {
Log.d(KOIN_TAG, "" + map.get(key))
return map.get(key)
}
class ExamTest : KoinTest {
#Test
fun getCharTest(){
assertEquals(Exam.getChar(0), 'a')
}
}
I debugged this test and it ran through the init of the Array, but the value is always null.
Please help me to solve this case. Thank you
The problem is, that SparseArray is part of Android and not of Java. In an unit test you are only able to use Plain old Java Objects and no android classes or dependencies (for example from package android.util).
Therefore you have to mock the SparseArray in your test.
For example you can use Mockito:
private var map: SparseArray<Char> = SparseArray()
map = mock()
Unfortunately you are not able to pass the mocked sparse array to your object.
So you cannot test your specific example like this.
Another solution would be to use the Robolectric framework if you want to test a class with platform dependencies. http://robolectric.org/

mocking internal classes with mockito

i have a class under Test which is marked as "interal"
internal class UnderTest{
fun methodToTest(){}
}
In my JUnit Test i want test UnderTest
import com.nhaarman.mockito_kotlin.mock
class SimpleTest{
val mock = mock<UnderTest>()
#Test
fun test(){
assertThat(....)
}
}
And here it gets a bit strange, as Android Studio complains first that UnderTest is not visible "public property exposes it's private type". Thats because UnderTest is marked as internal.
I changed the Test itself to be internal, which results that the compiler is happy again:
import com.nhaarman.mockito_kotlin.mock
internal class SimpleTest{
val mock = mock<UnderTest>()
#Test
fun test(){
assertThat(....)
}
}
Running this test results in an mockito exception like in the old mockito versions
Cannot mock/spy class com.name.UnderTest
Mockito cannot mock/spy because :
- final class
I want to write Unit Test for those internal classes, but how without removing the internal modifier from UnderTest class ?
Thanks
The issue is not that the class is internal (it's equivalent to public within the same module) but rather that it's final. By default all classes in Kotlin are final unless you mark them with open.
So if you want to mock your class you should mark it as internal open class Xyz.
Note that there's a Maven/Gradle plugin that automatically opens all classes for you: all-open plugin.
For example, given the following Kotlin class:
internal open class Foo
The following unit test passes:
class FooTest {
#Test
fun shouldPass() {
Mockito.mock(Foo::class.java)
}
}

Unit Testing AndroidViewModel classes

Im writting Unit Tests for my app and I've found a "speed bump" while writting them. While testing subclasses of AndroidViewModel im missing the Application parameter for its initialization. I've already read this question that uses Robolectric.
This is what I already tried so far:
Using Robolectric as the question describes. As far as I understand, Robolectric can use your custom Application class for testing, the thing is im not using a custom application class as I dont need it. (the app is not that complex).
Using mockito. Mockito throws an exception saying that the Context class cannot be mocked.
Using the InstrumentationRegistry. I moved the test classes from the test folder to the androidTest folder, giving me access to the androidTestImplementation dependencies, I tried using the InstrumentationRegistry.getContext() and parse it to Application, of course this didn't worked, throwing a cast exception. I felt so dumb trying this but again, it was worth the shot.
I just want to instanciate my AndroidViewModel classes so I can call their public methods, but the Application parameter is needed. What can I do for this?
fun someTest() {
val testViewModel = MyViewModelThatExtendsFromAndroidViewModel(**missing application parameter**)
testViewModel.foo() // The code never reaches here as the testViewModel cant be initializated
}
I had the same issue, and found two solutions.
You can use Robolectric in a Unit Test, inside test directory, and choose the platform Application class.
#RunWith(RobolectricTestRunner::class)
#Config(application = Application::class)
class ViewModelTest {
#Test
#Throws(Exception::class)
fun someTest() {
val application = RuntimeEnvironment.application
val testViewModel = MyViewModelThatExtendsFromAndroidViewModel(application)
testViewModel.foo()
}
}
Or you can use an InstrumentationTest inside androidTest directory, and cast the InstrumentationRegistry.getTargetContext().applicationContext to Application:
#RunWith(AndroidJUnit4::class)
class ViewModelTest {
#Test
#Throws(Exception::class)
fun someTest() {
val application = ApplicationProvider.getApplicationContext() as Application
val testViewModel = MyViewModelThatExtendsFromAndroidViewModel(application)
testViewModel.foo()
}
}
Hope it helped!

Integrating Robolectric and Cucumber

I want to combine both Robolectric and Cucumber (JVM).
Currently I have two classes ActivityStepdefs where two step definitions for activity management are defined.
My second class is RoActivity Where for example an activity is created from it's class name, and where Robolectric will be used.
When I run RoActivityTest using RobolectricTestRunner the test in this class passes, but when I run RunCukesTest (class for running features as junit test) the code from RoActivity is not running as part of Robolectric, i.e. RunCukesTest search for features on my project and match it with a method inside ActivityStepdefs and finally this class will call a method from RoActivity
Is possible to run test with both junit both* runners?
I'm not sure but perhaps it's possible to do something like powermock, using junit rules.
In that case for which one should I have to define the rule?
*Cucumber and Robolectric
My small 5 cents.
Cucumber is mostly used for acceptance tests (correct me if you use it for unit testing) and Robolectric is mostly used for unit testing.
As for me, it is overkill to write cucumber during TDD. And Robolectric is still not android and I would run acceptance tests on real device or at least emulator.
I'am facing the same problem, after some google work, I got a solution:
#RunWith(ParameterizedRobolectricTestRunner::class)
#CucumberOptions( features = ["src/test/features/test.feature","src/test/features/others.feature"], plugin = ["pretty"])
class RunFeatures(val index: Int, val name:String) {
companion object {
#Parameters(name = "{1}")
#JvmStatic
fun features(): Collection<Array<Any>> {
val runner = Cucumber(RunFeatures::class.java)
Cucumber()
val children = runner.children
return children.mapIndexed{index, feature ->
arrayOf(index,feature.name)
}
}
}
#Test
fun runTest() {
val core = JUnitCore()
val feature = Cucumber(RunFeatures::class.java).children[index]!!
core.addListener(object: RunListener() {
override fun testFailure(failure: Failure?) {
super.testFailure(failure)
fail("$name failed:\n"+failure?.exception)
}
})
val runner = Request.runner(feature)
core.run(runner)
}
}
but seems not an pretty solution for me, can somebody help me out these problem:
must explicitly list all feature file path. but cannot use pattern such as *.feature
when failed cannot know which step failed.
parameter can only pass primitive type data,
I've get into cucumber source , but seems CucumberOptions inline Cucumber , I cannot pass it programmatically but can only use annotation .

Categories

Resources