How to test AlertDialog using Mockk - android

I have a function inside my activity like this.
class MyActivity : AppCompatActivity() {
...
fun functionUndertested() {
AlertDialog.Builder(this, R.style.AppTheme)
.setTitle("MyTitle")
.create()
}
}
I want to test the functionUndertested() to verify whether .setTitle is successfully executed.
I am a new bee on the Mockk library, and I failed to find a similar example in the Mockk document. This issue has blocked me for some days.
I will be appreciated it if someone could tell me how to implement the test case.

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.

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!

How to call AppCompatActivity onCreate in unit test using Mockito

I want to write a simple unit test for the Android Activity class. The test runs fine while Activity is extended, however, when extending AppCompatActivity the test fails with:
java.lang.NullPointerException
at android.support.v7.app.AppCompatDelegateImplBase.(AppCompatDelegateImplBase.java:117)
at android.support.v7.app.AppCompatDelegateImplV9.(AppCompatDelegateImplV9.java:149)
at android.support.v7.app.AppCompatDelegateImplV14.(AppCompatDelegateImplV14.java:56)
at android.support.v7.app.AppCompatDelegate.create(AppCompatDelegate.java:202)
at android.support.v7.app.AppCompatDelegate.create(AppCompatDelegate.java:183)
at android.support.v7.app.AppCompatActivity.getDelegate(AppCompatActivity.java:518)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:70)
at activity.MainActivity.onCreate(MainActivity.kt:15)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at activity.MainActivity.onCreate(MainActivity.kt:15)
MainActivity code:
class MainActivity : AppCompatActivity() {
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
finish()
}
}
Test code using Mockito:
private lateinit var mainActivitySpy: MainActivity
#Before
fun setUp() {
mainActivitySpy = spy(MainActivity())
}
#Test
fun `Test case`() {
mainActivitySpy.onCreate(mock())
verify(mainActivitySpy).finish()
}
I have tried adding the following code but to no avail.
android {
...
testOptions {
unitTests.returnDefaultValues = true
}
}
Is there any way to make this code work using the only JUnit+Mockito and no other testing frameworks, like Roboelectric, etc?
I got this to work using PowerMock.
testImplementation 'org.powermock:powermock-api-mockito:1.6.6'
testImplementation 'org.powermock:powermock-module-junit4:1.6.6'
Then in my test class I have...
#RunWith(PowerMockRunner.class)
#PrepareForTest({ ReportFragment.class })
public class AddEditBeaconActivityTests {
#Test
public void test_onCreate() {
// Mock some data
mockStatic(ReportFragment.class);
MyActivity activity = spy(new MyActivity());
doNothing().when(activity).initScreen();
doNothing().when(activity).setContentView(R.layout.layout);
doReturn(mock(AppCompatDelegate.class)).when(activity).getDelegate();
// Call the method
activity.onCreate(null);
// Verify that it worked
verify(activity, times(1)).setContentView(R.layout.layout);
verify(activity, times(1)).initScreen();
}
}
Then here is what MyActivity.java looks like...
public class MyActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout);
initScreen();
}
}
This works because your are mocking some of the internal methods/classes that are called when you call super.onCreate(). More specifically...
The NullPointerException that you are seeing is because your mocked class doesn't have a AppCompatDelegate and so it tries to create one. If you passed in a mocked AppCompatDelegate with doReturn(mock(AppCompatDelegate.class)).when(activity).getDelegate(); you can get around this problem.
Eventually, the SupportActivity will try to call ReportFragment.injectIfNeededIn(this);. Simply mocking this static class with mockStatic(ReportFragment.class) will mock this call out. Don't forget to add the two lines to the top of your class: #RunWith(PowerMockRunner.class) and #PrepareForTest({ ReportFragment.class })
I am not going to argue whether you should be mocking the onCreate method or not. David makes a very valid argument. If you truly need to test the onCreate method in unit tests, this should do the trick.
Is there any way to make this code work using the only JUnit+Mockito and no other testing frameworks, like Roboelectric, etc?
No, there is no easy way to do this without resorting to a framework. And the real question is why you would even want to try. Tests against an Activity in which every single dependency is mocked start to degenerate into "testing tautologies" where the test is simply mirrors the logic of the Activity without adding any value.
The conventional wisdom in Android is to keep Activities, Fragments etc. as lightweight as possible since they are difficult to unit test. The business logic you need can be handled by a testable Presenter or ViewModel. Your book or tutorial should cover this.
If you must write a test against an Activity you can write an Instrumented Unit Test in which a real Activity is instantiated on a device. See the official documentation for how to do this.

Mockito Stubbed Spy sometimes calls and sometimes does not call the spied object methods

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.

Categories

Resources