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!
Related
I need a context in some unit test classes. I'm getting errors from 3rd SDKs when I try to provide an application class. So, I want to provide an empty application class for the unit tests.
#RunWith(RobolectricTestRunner::class)
class SampleTest {
private val context: Context = ApplicationProvider.getApplicationContext()
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)
}
}
If we just use plain dagger 2. In the application class, we will have a property which holds the AppComponent. Then we can swap it during espresso tests.
But when I setup my project using dagger-android 2.15. Things becomes more implicit if adopt too much Dagger magic. The code is more clean, but makes testing a little bit hard.
This is the application class:
class App : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
return DaggerAppComponent
.builder()
.create(this)
.build()
}
}
This is the HomeActivity
class HomeActivity : DaggerAppCompatActivity() {
#Inject
lateinit var userPreference: UserPreference
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
if (!this.userPreference.memberRegistered) {
goToActivity(EntryActivity::class.java)
}
}
}
Take this code for example. How to mock that injected userPreference.memberRegistered Which could be a HTTP call underneath?
For those who is interested in this, I got a blog with step by step detail for this:
Basically, the idea is:
You still generate instances for injection in #Module
But We’ll create new #Component A only for testing
This #Component will have a method to get that #Module
During tests, we swap the #Component that the application use with our component A
Then things are easy:
Without DaggerMock
In the #Module, instead of return real instance, you just return mockito mock.
With DaggerMock
You declare the type you want to swap and mock it
You can then use the mock.
No need to change the #Module
It inspires by #AutonomousApps 's solution, but the differences are now you don't need to write the #Component, #Module for each test class.
After trying several approaches, this is the only one that worked for me.
I wrote a blog post that explains how to do this just yesterday: https://dev.to/autonomousapps/the-daggerandroid-missing-documentation-33kj
I don't intend to repeat the entire post for this answer (it's hundreds of words and lines of code to properly set up a test harness with Dagger), but to attempt to summarize:
Add a custom application class in the debug source set (I assume it would also work in the androidTest source set, but I have not tried this).
You also need to reference this application in a AndroidManifest.xml in the same source set.
Create a "Test component" in your androidTest class that extends from your production top-level component and build it.
Use that test component to inject your application, which means you've just replaced your entire Dagger dependency graph with a new one you've defined just for the test suite.
Profit.
QUESTION
In a #Test, how can I achieve both;
Call a real method from a Kotlin class under test and
stub the inner calls it does to other methods within such class under test.
SCENARIO
I am using the following libraries;
testCompile "com.nhaarman:mockito-kotlin:1.5.0"
testCompile "org.mockito:mockito-inline:2.12.0"
I also have a simple kotlin class in the form of
class MyClass() {
fun parentFunc() {
funA()
}
fun funA() {
//DOES SOMETHING WHICH I ASSUME IS IRRELEVANT FOR ANSWERING THE QUESTION
}
}
TESTING WITH A SPY
#Test
fun myTest() {
val myClassSpy = spy(MyClass())
Mockito.doNothing().`when`(myClassSpy.funA())
//Mockito.doNothing().whenever(myClassSpy.funA()) also throws the same error
myClassSpy.parentFunc()
verify(myClassSpy, times(1)).funA()
}
Which throws the error,
org.mockito.exceptions.misusing.UnfinishedStubbingException:
Unfinished stubbing detected here:
-> at com.nhaarman.mockito_kotlin.MockitoKt.doNothing(Mockito.kt:108)
E.g. thenReturn() may be missing.
Examples of correct stubbing:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
Hints:
1. missing thenReturn()
2. you are trying to stub a final method, which is not supported
3: you are stubbing the behaviour of another mock inside before 'thenReturn' instruction if completed
Another Test case;
#Test
fun myTest() {
val myClassSpy = Mockito.spy(MyClass())
myClassSpy.parentFunc()
verify(myClassSpy, times(1)).funA()
}
gives the following error:
Wanted but not invoked:
myClass.funA();
However, there was exactly 1 interaction with this mock:
myClass.parentFunc();
Also, anytime I attempt to use the debugger to call myClassSpy methods or something related to it, it throws the following error:
com.sun.jdi.InternalException : Unexpected JDWP Error: 41
I have attempted to use
Mockito.`when`(myClassSpy.funA()).then { }
Mockito.`when`(myClassSpy.funA()).thenAnswer { }
Mockito.`when`(myClassSpy.funA()).thenReturn(Unit)
TESTING WITH A MOCK
Mocking the whole class does not work in this case because it is a mock and does not call the real method under test:
#Test
fun myTest() {
val myMock: MyClass = mock()
myMock.parentFunc()
verify(myMock, times(1)).funA()
}
Same error:
Wanted but not invoked:
myClass.funA();
However, there was exactly 1 interaction with this mock:
myClass.parentFunc();
If I further call the real method it also shows the same wanted but not invoked myClass.funA(); error:
#Test
fun myTest() {
val myMock: MyClass = mock()
Mockito.`when`(myMock.parentFunc()).thenCallRealMethod()
myMock.parentFunc()
verify(myMock, times(1)).funA()
}
I also, tried opening MyClassbut threw the same errors.
Thus, how can I stub methods from a spy so that when I test methods from such spied object it does not propagate the call to other methods which I do not want to further mock.
Any help, suggestion, idea... in order to test these type of methods is highly appreciated.
for me mocking kotlin classes with mockito sometimes works and sometimes it does not, so what i usually do is extract an for that class, and that always works.
also mocks are for testing interactions so you should not mock a method on the same class that you are testing but a different class that you pass to the constructor (dependency injection) then you can inject a mock at test time and the correct dependency at production time.
class MyTestedClass(val funManager:MyUsedClass) {
fun parentFunc() {
funManager.funA()
}
}
Solution:
When the code base is all in Kotlin the solution was:
Use MockK which is a library for testing and mocking Kotlin Code.
When the code base is written in both Java and Kotlin the solution was:
Use MockK for testing & mocking Kotlin and Mockito for testing & mocking Java code.
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 .