How to write test cases for class using netrypoint - android

I am evaluating if I can use hilt in my app. One blocker is to understand how to write test cases for class that is using entry points. I am not able to find anything related to it. Below is an example of a class method that I would like to unit test but I am not able to understand how to do it. Please help.
suspend fun processResponse(context: Context, requestType: String, response: NetworkResponse) {
val obj = EntryPointAccessors.fromApplication(
appContext, UtilitiesEntryPoint::class.java)
someDependency = obj.getSomeDependency()
//some business logic that need to be tested

Related

Unable to test kotlin interface with Robolectric

Currently, I am using a testing framework called Robolectric. And I was wondering what is the right way to test an interface to check the value and method for it. For example, In the following sample, I have an interface called TestInterface. And I wanted to test if this interface will work. So I set up like the following, by making the interface into an object. Is it a wrong way to do it ? Or is there a better approach? I'm not savvy with testing. So if there are some samples or hints, it will be so helpful. Thank you.
#RunWith(RobolectricTestRunner::class)
class MySampleTest {
#Test
fun `test mysample `() {
val testName = "Test Name"
val userInput = "User Input"
val testResults = object : TestInterface {
override val testValue: String
get() = testName
override fun createTest(input: String): String {
return userInput+input
}
}.createTest(userInput)
assertTrue(keyPair.verify(userInput, testResults))
}
As we are not talking about UI, saying "test interface" confuses me. We can write tests for implementations only. Otherwise what we can test at all?
There should be one or more implementations in you project (if don't, why do even need this interface?), so you can test them.

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/

Type inference failed: Not enough information to infer parameter T in fun <T : Context!> getApplicationContext(): T! Please specify it explicitly

I am trying to write some tests for my android app and it's really chalanging for me. One of many obsticles is this error
Type inference failed: Not enough information to infer parameter T in fun <T : Context!> getApplicationContext(): T! Please specify it explicitly.
which occures on this line
val actualIntent: Intent = shadowOf(ApplicationProvider.getApplicationContext())
.nextStartedActivity
Full test code looks like this
#Test
fun clickingLogin_shouldStartLoginActivity() {
val scenario = launch(LogInActivity::class.java)
scenario.onActivity { activity ->
activity.go_to_register_button.performClick()
val expectedIntent = Intent(activity, RegistrationActivity::class.java)
val actual: Intent = shadowOf(ApplicationProvider.getApplicationContext())
.nextStartedActivity
expectedIntent.component shouldBeEqualTo actual.component
}
}
Basically the shadowOf function is overloaded and can return many thinks and I need to specify the type.
I believe it should be somethink like shadowOf<SomeType>(...) But I have no idea what the actual type should be.
Any help would be really appreciated.
EDIT
I am following roboloctric guideline but trying to write it in an androidX way
An Intent is a different type of Object that does not extend from Context.
this line :
val actualIntent: Intent = shadowOf(ApplicationProvider.getApplicationContext())
provides a Context as an argument, and returns a ShadowContext, not an Intent.
docs ref : http://robolectric.org/javadoc/3.0/org/robolectric/Shadows.html#shadowOf-android.content.Context-
Basically it is telling you that a tree can't be a type of car.
I may not have asked as clearly as I should have. But for anyone who landed here stucked with the same problem here is a solution.
I did not figure out how to test if an activity is producing correct intents in normal tests. But in instrumented tests it goes like this:
#get:Rule
var activityRule: IntentsTestRule<MyActivity> =
IntentsTestRule(MyActivity::class.java)
#Test
fun testIntent () {
// perform some actions
// than verify
intended(hasComponent(OtherActicity::class.qualifiedName))
intended(hasExtra(A_CONSTANT, someValue))
}
You need a dependency for this to work
androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'
more info here

Kotlin - Mockito verify method calls

I'm trying my hand with Mockito for writing unit test's. I have a class that needs to be tested like below-
open class Employee {
fun setDetails(name: String, age: Int) {
setName(name)
setAge(age)
}
fun setName(name: String) { }
fun setAge(age: Int) { }
}
Below is my test class
class EmployeeTest {
#Mock
lateinit var emp: Employee
#Before
fun setup() {
MockitoAnnotations.initMocks(this)
}
#Test
fun testDetail() {
emp.setDetails("Henry", 23)
verify(emp, times(1)).setAge(23)
}
}
Here is my problem
When I do -
verify(emp, times(1)).setAge(23)
This give's me a success, because setAge is called once in setDetails() of Employee.kt. So that works fine for me
But, when I do-
verify(emp, never()).setAge(23)
This still gives me a success, even though the method is called in setDetails(). Shouldn't this test case fail?
Please help me understand this. I haven't been able to figure out why this happens.
EDIT
Here's what worked for me
I used a spy instead of a mock. However, I had to also declare the methods as open in Kotlin.
As mentioned by #kcoppock, your question includes an improper use of a mock. You should be using mocks to stub out dependencies in order to control their behavior.
In your case, the unit under test is the Employee class and its associated methods. In general, you do not want to mock out the unit under test because you want to know (from your unit test) if your class is behaving the way it should. To accomplish that, you'll want to use a real instance of an Employee, and not a mock.
If you are insistent on using verify on the Employee instance, you can create a spy.
#Test
fun setDetails_adjustsAge() {
val employee = spy(Employee())
employee.setDetails("Henry", 23)
assertEquals(23, employee.age)
verify(emp, times(1)).setAge(23)
}
Here are some references for further reading:
Mockito official documentation on spies:
http://static.javadoc.io/org.mockito/mockito-core/2.24.0/org/mockito/Mockito.html#13
Tutorial on how to use Mockito.spy
https://www.baeldung.com/mockito-spy
Differences between mock and spy: https://www.toptal.com/java/a-guide-to-everyday-mockito
So your issue here is that you don't actually want to use a mock. When you use a mock, you're required to define the behavior for any method that you call on that instance. So when you call emp.setDetails("Henry", 23), there is no implementation for that method so nothing happens. The behavior defined in the Employee class will not be used, as emp is just a fake instance of Employee that has not defined any behavior.
For your test scenario, you should prefer to use a real instance, and validate the end result rather than the internal behavior. For instance:
#Test
fun setDetails_adjustsAge() {
val employee = Employee()
employee.setDetails("Henry", 23)
assertEquals(23, employee.age)
}

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!

Categories

Resources