I have an implementation which is using a Integer as key in HashMap. It is already unit tested using JUnit. But I want to change it to SparseArray which is more optimised version from Android. I am not sure how will it be unit tested using JUnit. Does anyone have a better way to do this?
There's an equivalent implementation of SparseArray in Support Library called SparseArrayCompat that can be used in JVM Unit Tests. Also it has more features than the native one, so you're better off using that.
In case anyone is still looking, you don't need any third party libraries. You can simply mock SparseArray.
Create a file SparseArray.java inside app/src/test/java/android/util and paste the following:
package android.util;
import java.util.HashMap;
public class SparseArray<E> {
private HashMap<Integer, E> mHashMap;
public SparseArray() {
mHashMap = new HashMap<>();
}
public void put(int key, E value) {
mHashMap.put(key, value);
}
public E get(int key) {
return mHashMap.get(key);
}
}
Note: In case you are using funcations beside put and get you will need to implement those here as well.
Unmock is a really nice project that gives you this and more. https://github.com/bjoernQ/unmock-plugin
Just add the plugin and it will work.
Configuring tests with roboelectric framework may be simpler.
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
#RunWith(RobolectricTestRunner.class)
public class XxxTest {
...
}
Related
I am trying to test ViewModel to make sure livedata gets updated correctly. However when using ArgumentMatchers.any() it fails with IllegalStateException saying:
ArgumentMatchers.any(mViewModel.CountSubscriber::class.java) must not
be null
#Test
fun emitValueIfCountIs7() {
doAnswer { invocation: InvocationOnMock ->
val subscriber: mViewModel.CountSubscriber = invocation.getArgument(0)
subscriber.onNext(7)
null
}.`when`(countUseCase).execute(
ArgumentMatchers.any(mViewModel.CountSubscriber::class.java),
ArgumentMatchers.any(Parameters::class.java)
)
// When
mViewModel.getCount()
// Verify
assert(mViewModel.countResponse.value != null)
}
I am using Kotlin and have the following dependencies:
testImplementation 'junit:junit:4.12'
testImplementation "org.mockito:mockito-inline:2.23.4"
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0"
Here are my imports:
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nhaarman.mockitokotlin2.doAnswer
import io.reactivex.Observable
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
import org.mockito.invocation.InvocationOnMock
Strange thing is that it used to work before, and I don't know what has happened that could affect this.
Getting matchers to work with Kotlin can be a problem.
If you have a method written in kotlin that does not take a nullable parameter, then we cannot match with it using Mockito.any().
This is because it can return void and this is not assignable to a non-nullable parameter.
If the method being matched is written in Java, then I think that it will work as all Java objects are implicitly nullable.
One possible solution would be to use a library like mockito-kotlin
But you can solve this issue easily with a few lines of code yourself.
If you need typed any(type: Class)
private fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
OR
You can use this matcher instead of Matchers.any() :
object MockitoHelper {
fun <T> anyObject(): T {
Mockito.any<T>()
return uninitialized()
}
#Suppress("UNCHECKED_CAST")
fun <T> uninitialized(): T = null as T
}
and use MockitoHelper.anyObject() instead of any() in your kotlin tests.
You can find more information in this post: Using Mockito with Kotlin
There is a discussion about possible solutions in this post :
Is it possible to use Mockito in Kotlin?
The correct solution is mentioned in the comment section of the question by #Ana Koridze. Yes, if you are using Koltin + mockitoKotlin. Make sure you are using the following import:
1 - Using the Mockito-kotlin:
import org.mockito.kotlin.any from Mockito-kotlin
instead of
import org.mockito.Mockito.any
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
2 - Or if you are using older mockito kotlin version original created by nhaarman before the intergation
import com.nhaarman.mockitokotlin2.any from nhaaram's Mockito-kotlin instead of import org.mockito.Mockito.any
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:$mockito_kotlin2_version"
BTW, if you are using Android Studio or IntelliJ IDEA. the any() from mockitokotlin library should be italic by default style/color scheme.
Notice the any() at the end of line. This is from mockitokotlin
And here is the any() from mockito
Thanks #Sattar for the recommended edit.
mockito-kotlin has added support for nullable args with anyOrNull()
`when`(myMock.doesThis(any(), anyOrNull())).thenReturn(someObject)
use Mockito-kotlin
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"
this will use any that works for kotlin as this is a wrapper lib for Mockito but for kotlin
This is what worked for me,
either replaced all generic any()s , with a specific anyTYPE(), i.e anyInt(), anyList() etc from core lib org.mockito:mockito-core and it fixes the (nullability)issue, it seems the specific definitions with type can handle null. this option does not require you to import any extra lib
or
if you really need to use the generic type any() , add this official Mckito extension lib https://github.com/mockito/mockito-kotlin and make sure you use the any() from this lib (by making sure your imports has this in it import org.mockito.kotlin.any)
I suggest to start using MockK lib https://github.com/mockk/mockk instead of Mockito as it is a Mock library for Kotlin = MockK)
however, if you feel lazy to switch right now or maybe dealing with legacy tests (as in my case :), this should fix your issue too.
For me all solutions above were not enough - in addition to that I had to mark the called method as an 'open' method.
According to this:
https://github.com/mockito/mockito-kotlin/wiki/Parameter-specified-as-non-null-is-null
The method is final and Mockito couldn't mock it so I had to add 'open'.
I wrote a simple wrapper function around Mockito's any() and got rid of the warning.
private fun <T> any() : T {
return org.mockito.ArgumentMatchers.any()
}
I am still pretty green at Kotlin though, so I am not sure whether there may be some unwanted side effects. I put this answer out there in case it helps anyone or someone gives me feedback.
I have written the following annotation:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.SOURCE)
#Target({ElementType.METHOD})
public #interface Warning {
}
Which is intended to annotate methods which can cause problems if called carelessly. I added an annotation processor to my project, but this only provides the warning in the log output of the javac command. I want this warning to appear in Android Studio along with the other lint warnings anywhere a method with this annotation is called. This is why I am trying to write a custom lint rule. I have the basic skeleton of the lint rule:
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
public class CaimitoDetector extends Detector implements Detector.JavaScanner {
public static final Issue ISSUE = Issue.create(
"WarningAnnotation",
"This method has been annotated with #Warning",
"This method has special conditions surrounding it's use, be careful when using it and refer to its documentation.",
Category.USABILITY, 7, Severity.WARNING,
new Implementation(CaimitoDetector.class, Scope.JAVA_FILE_SCOPE));
#Override
public void visitMethod(JavaContext context, AstVisitor visitor, MethodInvocation node) {
}
}
import com.android.tools.lint.client.api.IssueRegistry;
import com.android.tools.lint.detector.api.Issue;
import java.util.Collections;
import java.util.List;
public class CaimitoIssueRegistry extends IssueRegistry {
#Override
public List<Issue> getIssues() {
return Collections.singletonList(CaimitoDetector.ISSUE);
}
}
But I do not know how to proceed from here. How can I check if an annoation exists on a method, and raise a warning such that it will be visible in Android Studio?
But I do not know how to proceed from here
I suggest to write a test for your Detector first. Here is an example project which demonstrates how to write Detector tests [1]. That way you can try and adjust your Detector as you like.
How can I check if an annoation exists on a method
I suggest to have a look at Android's default detectors [2]. There you'll most probably find a good point to start. E.g. the AnnotationDetector.
and raise a warning such that it will be visible in Android Studio?
If you integrate your custom rules correctly into your project, then Lint will raise the warning for you. Please have a look here [3] for different options on how to integrate custom rules in your project. Note: AFAIK warnings of custom rules will only reported when running the corresponding Gradle task. The "auto-highlight" of Android Studio does not work with custom rules.
https://github.com/a11n/CustomLintRules
https://android.googlesource.com/platform/tools/base/+/master/lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks
https://github.com/a11n/android-lint/tree/master/6_application
The follows was the code which I want to test.
public class Demo {
private static final List<Pair<String, String>> mList;
static {
mList = new ArrayList<>();
mList.add(new Pair<>("F0", "T1"));
mList.add(new Pair<>("F1", "T2"));
mList.add(new Pair<>("F2", "T3"));
}
public String getStr(int pos) {
return mList.get(pos).first;
}
}
I was an android developer. I have get some trouble in test and mock the code.I have use mockito.
I have try some code to test it,but the result was not my expect.
1.First try
#Test
public void test(){
Demo demo=new Demo();
assertEquals(demo.getStr(0),"F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
2.Second try
#Test
public void test() {
Demo demo = mock(Demo.class);
doCallRealMethod().when(demo).getStr(0);
assertEquals(demo.getStr(0), "F0");
/**
* java.lang.AssertionError:
* Expected :null
* Actual :F0
*/
}
Anyone tell me how can I resolve this problem to make demo.getStr(0) == "F0" by call the real method? Thanks!
===========================
Another question relate to it
I have try an another test to test android.util.Pair class, and the result is that "pair.first" was null,.(There are androidTest and test directory,I put it into test package.Did it impact the result?)
import android.util.Pair;
import org.junit.Test;
import org.mockito.Mockito;
import static org.junit.Assert.assertEquals;
public class DemoTest {
#Test
public void test1(){
Pair<String,String> pair=new Pair("First","Second");
assertEquals("First",pair.first);
//pair.first was null,why?
}
#Test
public void test2(){
Pair<String,String> pair= Mockito.spy(Pair.class);
assertEquals("First",pair.first);
//pair.first was null also,why?
}
}
Why the simple code is correct in real android environment,but failure in test?
I had the same problem too. month ago I have problem with TextUtils class too.
I report this to jUnit but they told me the problem is with android package because in unit test environment you don't have access to platform specific classes
for that pair case you can use this package. this works for me
import android.support.v4.util.Pair;
The problem in your first try is, that the public field "first" is actually null.
Is the Pair class the one from the "javafx.util" package or a custom implementation?
Did you forget "this.first = first" or something similar in the constructor of the "Pair" class?
I would also recommend to change the following line:
assertEquals(demo.getStr(0),"F0");
to
assertEquals("F0", demo.getStr(0));
so that the error is printed correctly.
Your second try does not make any sense. What is the point in mocking the class you want to test?
I think the second example has the same problem as the first one. Pair.first is never set. If you fix that, it should also work (untested).
From Google's Android tools website:
"Method ... not mocked."
The android.jar file that is used to run unit tests does not contain any actual code - that is provided by the Android system image on real devices. Instead, all methods throw exceptions (by default). This is to make sure your unit tests only test your code and do not depend on any particular behaviour of the Android platform (that you have not explicitly mocked e.g. using Mockito).
So how can we solve this?
In other words. If you need a default android class to work properly you either have to include it from a separate repository, or implement it yourself.
In the case of Android's Pair class. You can use android.support.v4.util.Pair instead.
To get access to this class, you can include com.android.support:support-compat:27.0.0 in your build.gradle or dependencies file.
If you are not using Gradle, you can copy the implementation of this file and use it in place of the official one. Or you can try and download the .jar file from this older version https://mvnrepository.com/artifact/com.google.android/support-v4/r7 (I have not tested whether it works)
Another approach (based on this) is to create the class in app/src/test/java/android/util/Pair.java and copy the code from the Android implementation.
This way you don't need extra dependencies. (There may be issues related to the implementation changing after you make the copy, but the dependencies may become stale as well.)
I have created a simple activity in which I have 2 buttons for submitting and cancel. I just want to test this project by using UI Automator testing. So I create an android test project and make a class. I make this test class extend UiAutomatorTestCase. I have also added uiautomator.jar, android.jar as well as the junit3 library. But when I run the test case, it gives me an error about
TestSuiteConstruction failed and java.lang.RuntimeException.
However I add constructor but as it does not take any parameter, so I am unable to add any paramater. Here is my test case code. Please solve this error as soon as possible. Can you please tell me anything that I have not added in my project?
package com.example.automatorapp.test;
import android.test.suitebuilder.TestSuiteBuilder;
import android.util.Log;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
import com.example.automatorapp.MainActivity;
public class testDemo1 extends UiAutomatorTestCase
{
public testDemo1()
{
}
public void testdemo() throws UiObjectNotFoundException
{
getUiDevice().pressHome();
Log.e("how r u","hello");
}
}
Try changing the name of your class so that is does not start with 'test' and remove the constructor. By convention, the name of a class starts with a Capital letter, yours starts with the lowercase 't'. Also, again by convention, JUnit3 test classes put the word 'Test' at the end. http://junit.sourceforge.net/junit3.8.1/javadoc/junit/framework/TestCase.html So in your example a class name of testdemo1 would be something line Demo1Test (remember the filename needs to match the classname).
However, and to my surprise, a slightly modified version of your code ran on my machine. Here's the code that ran OK.
package com.example.automatorapp.test;
import android.util.Log;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;
public class testDemo1 extends UiAutomatorTestCase
{
public testDemo1()
{
}
public void testdemo() throws UiObjectNotFoundException
{
getUiDevice().pressHome();
Log.e("how r u","hello");
}
}
I got the Log message in the Android log too.
E/how r u (24667): hello
Therefore, you may have some issues with your project setup or build environment. As far as I know this project (for testing) should be independent of the code or project for the app you want to test. However you have an import to the app you want to test.
import com.example.automatorapp.MainActivity;
As you've now posted quite a few questions related to UI Automator perhaps you could summarise your progress so far. For example, have you ever got a UI Automator test to run successfully?
PS: I hoped to see some feedback to answers to your earlier questions. Without your feedback it's hard to know which way you're heading and whether the answers were relevant or useful to you.
Is there any way I can Mock Static Function in Android using any Mocking Framework.
Mockito can mock classes but is insuffiecient to mock Static functions.
Any help will be highly appreciated.
Thanks in Advance
Mocking works by using the concepts of Object Orientation, Inheritance etc....
Basically by overriding certain methods & behaviour in objects / instances that look like real objects, because they are subclasses of these real objects.
In other words, the mocking part comes in overriding methods on instances.
It is not possible to override a static method (afaik).
Therefore mocking of static calls is not easy (if even possible).
EDIT - I was wrong...
As it turns out, I was wrong in my above statement that it is not possible.
I should have searched this site for duplicate questions. See below for some links to frameworks that claim to do this for you in some cases. Since they work with bytecode, I'm not sure they will work properly on Android (ymmv).
Mocking Static Methods
How can I easily mock out a static method in Java (jUnit4)
(thanks to Rohit for forcing me to reassess my beliefs)
Please try this instead: https://bintray.com/linkedin/maven/dexmaker-mockito-inline-extended
It helps me successfully mock the static method in the Android Instrumented Tests, but note that this feature requires running on a device with at least Android P.
Here is what I did:
Replace the androidTestImplementation 'org.mockito:mockito-android:2.28.0' with androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito-inline-extended:2.28.0'
Then mock the static method like this:
static class StaticTrojan {
static String staticOpen() { return "horse"; }
}
#Test
public void testStubbingStaticMethod() {
MockitoSession session = mockitoSession().spyStatic(StaticTrojan.class).startMocking();
try {
when(StaticTrojan.staticOpen()).thenReturn("soldiers");
assertEquals("soldiers", StaticTrojan.staticOpen());
} finally {
session.finishMocking();
}
// Once the session is finished, all stubbings are reset
assertEquals("horse", StaticTrojan.staticOpen());
}
If you use Kotlin, then to mocking static functions you can connect the mockk library project:
androidTestImplementation "io.mockk:mockk-android:1.12.0"
Then you need to add a AndroidManifest.xml to the androidTest directory if your tests are located in the application module.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="you.application.package">
<application
android:debuggable="true"
android:extractNativeLibs="true" />
</manifest>
Then you can mocking static functions using the following code:
import io.mockk.every
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import org.junit.Assert.assertEquals
import org.junit.Test
class TestMockingStaticFunction {
object StaticTrojan {
#JvmStatic
fun staticOpen(): String {
return "horse"
}
}
#Test
fun testMockingStaticFunction() {
assertEquals("horse", StaticTrojan.staticOpen())
mockkStatic(StaticTrojan::staticOpen)
val mockScope = every { StaticTrojan.staticOpen() } returns "solders"
assertEquals("solders", StaticTrojan.staticOpen())
unmockkStatic(StaticTrojan::staticOpen)
assertEquals("horse", StaticTrojan.staticOpen())
}
}
Library API allows you to comfortably mock the Kotlin objects, in the example above the object is used only to create a static function using #JvmStatic annotation.
Attention! This approach uses JVMTI available in Android P starting from the API level 28. Your application can be written using a smaller API, but the tests you must run only on Android P devices or newer.