How to unit test POJO which uses Android SDK classes on JVM? - android

I have a Java class called Warehouse which is using Android's Bundle. When trying to unit test it, I get the following exception:
java.lang.RuntimeException: Stub!
The class looks like that:
public class Warehouse {
private Bundle items;
public Warehouse(Bundle items) {
this.items = items;
}
}
In the unit test I have the following:
void testInitializeWarehouse() {
Bundle items = new Bundle();
Warehouse wh = new Warehouse(items);
assertNotNull(wh);
}
When I run the test I get java.lang.RuntimeException: Stub!
I know this issue is related to me running the unit test on JVM rather than Dalvik VM, but is there a way to run this unit test on JVM?

Try using Powermock to mock Bundle using its ability to mock a final class as described here - Powermock MockFinal

I found out Robolectric, which is exactly what I was looking for. I could have used mocks but then half of the unit tests will consist of mocks setups etc.
"a unit test framework that de-fangs the Android SDK jar so you can
test-drive the development of your Android app. Tests run inside the
JVM on your workstation in seconds."

Related

How mock Companion object in Android using Mockito

I been trying to run a unite test and I am now facing some issue in mocking the Application Context. I tried mockStatic() but is not working. I am using Junit 5 and org.mockito:mockito-inline:3.4.6 for testing.
class ApplicationContext : Application() {
init {
instance = this
}
companion object {
private var instance : ApplicationContext? = null
fun applicationContext() : Context = instance!!.applicationContext
}
When I run my test this throws a NullPointerException.
java.lang.NullPointerException
at com.adaptavant.yoco.Util.ApplicationContext$Companion.applicationContext(ApplicationContext.kt:14)
at com.adaptavant.yoco.viewModel.LoginViewModelTest.setUp(LoginViewModelTest.kt:48)
Can Someone help me out here.
There is no ApplicationContext available in unit test - you need to use instrumentation tests for that:
Local unit tests:
Use these tests to minimize execution time when your tests have no Android framework dependencies or when you can mock the Android framework dependencies.
Instrumented tests
These tests have access to Instrumentation APIs, give you access to information such as the Context of the app you are testing, and let you control the app under test from your test code.
Source

Is there a way to write a unit test for a target API

I am in the process of writing Android instrumented tests in an area were Robolectric custom shadows fall short.
Ideally, we want to write code that is flexible across all versions of the Android SDK however I am in an edge case situation where I need to write a individual unit test for a method that works only Marshmallow.
I also need to write a unit test that only works for Lollipop and under because of differences in operating system dependencies (i.e. java.lang.NoClassDefFoundError's)
Is there anyway I can do this through Junit4-like annotations or something similar to what Robolectric does where it can ignore running the tests if the SDK is not a good fit?
I do not want to be writing code like this:
// MarshmallowWidgetTest.java
#Test
public void testPrintName()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
{
// ...asserts...
}
}
// LollipopWidgetTest.java
#Test
public void testPrintName()
{
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP)
{
// ...asserts...
}
}
I'm not very familiar with unit testing or Robolectric, but because at time of writing unit tests by me there was no support for API 23 I used that config:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21) //this guy
public class MainActivityTest {
MainActivity_ activity = Robolectric.setupActivity(MainActivity_.class);
}
So like you see there's a annotation which you can use to your test classes.
EDIT:
Sorry that I focused only on Robolectric test framework, not main problem.
For annotating instrumentation tests for specific API I would use:
1. Class with #Before annotation
Create a class with #Before annotation, where it would check the API of tested devices. If wrong, the tests would fail in this method. Use fail(); method.
2. Use #SdkSuppress annotation
Indicates that a specific test or class requires a minimum API Level to execute.
Test(s) will be skipped when executed on android platforms less than specified level.
From: http://developer.android.com/reference/android/support/test/filters/SdkSuppress.html
So if you would set #SdkSuppress(minSdkVersion=23) it would run only on Android Marshmallow devices and if ##SdkSuppress(minSdkVersion=20) it would run only on higher 5.0 API devices.
Read also: http://www.vogella.com/tutorials/AndroidTesting/article.html
3. Create your own annotation like #SdkOnly
Maybe this article would be useful: http://help.testdroid.com/customer/portal/articles/1256803-using-annotations-in-android-instrumentation-tests
4. Create suites for your specific instrumentation tests
For this purpose you would use #RunWith() and Suites.SuiteClasses() annotations.
To organize the execution of your instrumented unit tests, you can
group a collection of test classes in a test suite class and run these
tests together. Test suites can be nested; your test suite can group
other test suites and run all their component test classes together.
A test suite is contained in a test package, similar to the main
application package. By convention, the test suite package name
usually ends with the .suite suffix (for example,
com.example.android.testing.mysample.suite).
To create a test suite for your unit tests, import the JUnit RunWith
and Suite classes. In your test suite, add the #RunWith(Suite.class)
and the #Suite.SuitClasses() annotations. In the #Suite.SuiteClasses()
annotation, list the individual test classes or test suites as
arguments.
The following example shows how you might implement a test suite
called UnitTestSuite that groups and runs the
CalculatorInstrumentationTest and CalculatorAddParameterizedTest test
classes together.
import com.example.android.testing.mysample.CalculatorAddParameterizedTest;
import com.example.android.testing.mysample.CalculatorInstrumentationTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
// Runs all unit tests.
#RunWith(Suite.class)
#Suite.SuiteClasses({CalculatorInstrumentationTest.class,
CalculatorAddParameterizedTest.class})
public class UnitTestSuite {}
From: http://developer.android.com/training/testing/unit-testing/instrumented-unit-tests.html
5. Helpful resources
http://www.netmite.com/android/mydroid/development/pdk/docs/instrumentation_testing.html
https://github.com/googlesamples/android-testing-templates/blob/master/AndroidTestingBlueprint
Hope it help

Method setUp in android.test.AndroidTestCase not mocked

I'm trying to come to terms with the new unit test feature of Android Studio.
I've followed the instructions on http://tools.android.com/tech-docs/unit-testing-support. The description there explicitly mentions the 'Method ... not mocked' error and suggests to put the following into the build.gradle:
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
This works in so far as the tests run when started from the command line with
gradlew test --continue
but not when I run the test class from Android Studio with rightclick -> run. This way, I get the same error again:
java.lang.RuntimeException: Method setUp in android.test.AndroidTestCase not mocked. See https://sites.google.com/a/android.com/tools/tech-docs/unit-testing-support for details.
at android.test.AndroidTestCase.setUp(AndroidTestCase.java)
at org.junit.internal.runners.JUnit38ClassRunner.run(JUnit38ClassRunner.java:86)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Any ideas on how to solve this?
EDIT: The content of the test class doesn't really matter because the setUp of the test fails, I tried with the most simple class:
public class ContactFormToolTest extends AndroidTestCase {
public void testSOmething(){
assertEquals(false, true);
}
}
Also tried overriding setUp, makes no difference.
From: https://sites.google.com/a/android.com/tools/tech-docs/unit-testing-support#TOC-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). If that proves problematic, you
can add the snippet below to your build.gradle to change this
behavior:
android {
// ...
testOptions {
unitTests.returnDefaultValues = true
}
}
The new Unit Tests feature in Android Studio fakes the entire Android SDK so that you can run fast, Java-only tests, without needing to install your application on an Android device (this is similar to Robolectric). The general idea is that you mock all the responses from the Android SDK calls.
AndroidTestCase is used to run a test with the real Android SDK.
So, your issue is that you are trying to run an AndroidTestCase that depends on the Android SDK, but your test runner is launching the Unit Tests environment, which uses a fake Android SDK instead of a real one.
You need to choose one approach. If you want a pure unit test, then you probably should use a JUnit 4 test class instead of an AndroidTestCase. More instructions here:
https://developer.android.com/training/testing/unit-testing/local-unit-tests.html#build
As of SDK version 24, AndroidTestCase is deprecated
This class was deprecated in API level 24.
Use InstrumentationRegistry instead. New tests should be written using
the Android Testing Support Library.
You are supposed to use the Espresso framework for UI testing. There is a tutorial.

Can I use Robolectric to unit test static library jars?

I'm trying to test a method in a jar library, and was hoping to use Robolectric to do my unit testing, rather than running the tests in the Android emulator. I'm running into a problem though, where Robolectric needs an androidmanifest.xml file that doesn't exist, since I'm building a library...
Is there any way to run Robolectric tests without an app?
Here's what my test case and code under test look like:
public class ObjectUnderTest {
methodUnderTest(View v) {
...
}
}
#RunWith(RobolectricTestRunner.class)
public class Tests {
#Test
public void methodUnderTest_Test() {
...
}
}
When I run the test suite I get a FileNotFoundException from Robolectric looking for androidmanifest.xml. I've tried using the JUnit4 test runner instead, but then I get the "Stub!" exception when I create a View for the argument to methodUnderTest().
Is there a way to do this besides creating a stub application just for the unit tests? Thanks!
It depends which Robolectric you're using.
If you use 2.0 you could try to annotate your test class with #Config(manifest=Config.NONE).
If you use 1.x I think it's doable but will require more effort:
You should folder create structure similar to Android project and create dummy AndroidManiifest.xml inside
You should extend RobolectricTestRunner and pass through constructor path to this fake Android project

Parameterized JUnit tests in Android test project

When I create parameterized test cases in JUnit 3.x, I usually create a TestSuite with something like
public static Test suite() {
TestSuite s = new TestSuite();
for (int i = MIN; i < MAX; ++i) {
s.addTest(new MyTest(i));
}
}
This suite() method is called correctly when running JUnit from a desktop command-line. When I tried this with my Android test project, the tests don't run. How do I get my tests to run on the emulator? Or is there a different way to create parameterized tests for Android?
More thoughts:
Typically I run my tests with the command line:
adb shell am instrument -w [-e class <fully qualified test class name>[#<test method name>()]] <Android package name>/android.test.InstrumentationTestRunner
This allows me to select which tests to run from my test suite. Ideally, I want to run the the parameterized tests in this way as well. The link in the comment from #Appu describes building a separate app that runs JUnit tests. As part of that, this app has a custom TestRunner. I can very likely borrow these ideas to create a TestRunner which I can use in place of android.test.InstrumentationTestRunner. This seems like a lot of work for a not uncommon task. I prefer not to reinvent the wheel if there is already a similar solution in the Android API. Does anyone know of such a thing? Also, other alternative solutions will be helpful.
Nevermind, it looks like #dtmilano already posted this as an answer...
You can implement a test runner to be able to pass parameters to Android tests.
See the example at how to pass an argument to a android junit test (Parameterized tests).
Or is there a different way to create parameterized tests for Android?
We (Square) wrote a library called Burst for this purpose. If you add enum parameters in your test constructor, Burst's test runner will generate a test for each combination of enum values. For example:
public class ParameterizedTest extends TestCase {
enum Drink { COKE, PEPSI, RC_COLA }
private final Drink drink;
// Nullary constructor required by Android test framework
public ConstructorTest() {
this(null);
}
public ConstructorTest(Drink drink) {
this.drink = drink;
}
public void testSomething() {
assertNotNull(drink);
}
}
Quite a while after originally writing this question, I discovered that I can directly run a test class which contains a static suite() method:
adb shell am instrument -w -e class <fully qualified test class name> <Android package name>/android.test.InstrumentationTestRunner
However, the test suite doesn't run when I try to run all the tests in a given package.
Of course, this has been a while. Now I am using Android Studio instead of the command-line. I can still run the test class individually, but it still doesn't run when I select a package or try to run all of my tests.
A potential alternative is to write a master test class with a suite() method which adds all the tests to the returned TestCase. Unfortunately, this requires some manually editing every time I add a new test class to my suite.

Categories

Resources