Android ArraySet in unit tests is not mocked - android

When using android.util.ArraySet in code, I cannot test the classes using it in my non-android test classes, because it would throw an exception:
java.lang.RuntimeException: Method add in android.util.ArraySet not mocked. See http://g.co/androidstudio/not-mocked for details.
The link says:
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)
How can I unit test code using ArraySet? I would say somehow mocking (Mockito, PowerMock) it by somehow "replacing it with a HashSet" could be promising:
Code to be tested:
Set<Bird> birds = new ArraySet<>();
birds.add(currentBird);
Test code:
whenNew(ArraySet.class).withAnyArguments().thenAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return new HashSet();
}
});
This gives java.lang.ClassCastException: java.util.HashSet cannot be cast to android.util.ArraySet.
A workaround would be to not to return a HashSet but some MyFakeArraySet extends ArraySet (which internally uses a HashSet), but sadly ArraySet is final. :-(

you should not use android sdk classes in your non-android test classes.
but if you want , you can use something like this :
#Test
public void testArraySet() {
final Set<Bird> fakeBirds = new HashSet<>();
ArraySet<Bird> birds = (ArraySet<Bird>) Mockito.mock(ArraySet.class);
when(birds.add(any(Bird.class))).then(new Answer<Boolean>() {
#Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
Bird param = invocation.getArgument(0);
return fakeBirds.add(param);
}
});
when(birds.contains(any(Bird.class))).then(new Answer<Boolean>() {
#Override
public Boolean answer(InvocationOnMock invocation) throws Throwable {
Bird param = invocation.getArgument(0);
return fakeBirds.contains(param);
}
});
Bird bird = new Bird();
birds.add(bird);
assert birds.contains(bird);
}

I've found out that the classes in the test folders seem to have precedence. So I do not have to mock anything. I can just place a package android.util into my test folders and an ArraySet class completely specified by me:
package android.util;
import android.support.annotation.NonNull;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ArraySet<E extends Object> implements Collection<E>, Set<E> {
private final HashSet<E> HASH_SET;
public ArraySet(int capacity) {
Log.e("ArraySet", "WARNING, using fake array set!");
HASH_SET = new HashSet<>(capacity);
}
#Override
public int size() {
return HASH_SET.size();
}
// Do this with all other methods as well: Chain them into HASH_SET.
}

Related

How do I provide a custom Application class to my Espresso Activity test?

I'm pretty new to Espresso, but I am trying to test a relatively simple Activity. My android app has its own custom Application class. How can I tell Espresso to use a mocked (or custom) version of this class?
Here is my custom version of the Application. It creates some test data (edited here for brevity). Down the road, I will also be overriding some of the methods.
public class MockMyApplication extends MyApplication {
#Override
public void onCreate() {
super.onCreate();
// create some location data for testing
DataRecord rec = new DataRecord(1);
rec.setName("TestLoc1");
rec.setDescription("an important customer");
MyData.add(rec);
}
}
My attempt to test using this, looks like this:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LocEditActivityTest extends AndroidJUnitRunner {
#Rule
public ActivityTestRule<LocEditActivity> activityTestRule
= new ActivityTestRule<>(LocEditActivity.class);
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
#Test
public void testActivity_ExistingLoc() {
Intent i = new Intent();
i.putExtra("loc",1);
activityTestRule.launchActivity(i);
onView(withId(R.id.editName)).check(matches(withText("TestLoc1")));
// shutdown
onView(withContentDescription("Navigate up")).perform(click());
}
}
Using a debugger, I have determined that when LocEditAcitivity's onCreate calls getApplication(), it returns a MyApplication class with empty data, and not the MockedMyApplication with my test data.
Found it!
Looks like I misunderstood the "Runner" class usage. I needed to create my own Runner that extended AndroidJUnitRunner:
import android.app.Application;
import android.content.Context;
import android.support.test.runner.AndroidJUnitRunner;
// Our own test runniner - uses MockMyApplication as a mocked app class
public class MyAndroidTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
}
And then in build.gradle (app), the testInstrumentationRunner entry needs to point to the new runner:
testInstrumentationRunner "com.winwaed.xyzapp.MyAndroidTestRunner"
As the newApplication override was in the wrong place, this should be removed from my test class. Also, the test class no longer extends any classes. (ie. I essentially split the runner and test classes - as I said, I misunderstood the runner class)

Class has no public constructor TestCase(String name) or TestCase() while running my Cucumber scenario

I'm using Green Coffee library to run Cucumber scenarios in my instrumentation tests. I followed example provided by repo step-by-step, but here's the error:
junit.framework.AssertionFailedError: Class pi.survey.features.MembersFeatureTest has no public constructor TestCase(String name) or TestCase()
And when I try to add default constructor to the class like provided here, it says
no default constructor available in
'com.mauriciotogneri.greencoffee.GreenCoffeeTest'
Here's my test's source code:
package pi.survey.features;
import android.support.test.rule.ActivityTestRule;
import com.mauriciotogneri.greencoffee.GreenCoffeeConfig;
import com.mauriciotogneri.greencoffee.GreenCoffeeTest;
import com.mauriciotogneri.greencoffee.Scenario;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.io.IOException;
import pi.survey.MainActivity;
import pi.survey.steps.memberSteps;
#RunWith(Parameterized.class)
public class MembersFeatureTest extends GreenCoffeeTest {
#Rule
public ActivityTestRule<MainActivity> activity = new ActivityTestRule<>(MainActivity.class);
public MembersFeatureTest(Scenario scenario) {
super(scenario);
}
#Parameterized.Parameters
public static Iterable<Scenario> scenarios() throws IOException {
return new GreenCoffeeConfig()
.withFeatureFromAssets("assets/members.feature")
.scenarios();
}
#Test
public void test() {
start(new memberSteps());
}
}
And my members.feature source:
Feature: Inserting info to server
Scenario: Invalid members
When I introduce an invalid members
And I press the login button
Then I see an error message saying 'Invalid members'
Regarding the questions about the constructors. Due to the fact that tests in GreenCoffee require:
#RunWith(Parameterized.class)
The static method annotated with #Parameters must return a list of something (but not necessarily Scenario). The examples in the documentation simply return a list of scenarios, that's why the constructor must take a single Scenario as a parameter.
However, you can create a class that encapsulates the scenario and other objects that you may need to pass to the constructor. For example, given the following class:
public class TestParameters
{
public final String name;
public final Scenario scenario;
public TestParameters(String name, Scenario scenario)
{
this.name = name;
this.scenario = scenario;
}
}
You can write:
public TestConstructor(TestParameters testParameters)
{
super(testParameters.scenario);
}
#Parameters
public static Iterable<TestParameters> parameters() throws IOException
{
List<TestParameters> testParametersList = new ArrayList<>();
List<Scenario> scenarios = new GreenCoffeeConfig()
.withFeatureFromAssets("...")
.scenarios();
for (Scenario scenario : scenarios)
{
testParametersList.add(new TestParameters(scenario.name(), scenario));
}
return testParametersList;
}
In this way you can receive multiple values (encapsulated in an object) in the test constructor.
Solved problem by just fixing the structure.
code details in this commit

How to create custom shadows in robolectric 3.0?

I need to mock some custom class (create for it a shadow).
I have already read on http://robolectric.org/custom-shadows/ how to do this.
so, i have some class:
public class MyClass {
public static int regularMethod() { return 1; }
}
I create a shadow:
#Implements(MyClass.class)
public class MyShadowClass {
#Implementation
public static int regularMethod() { return 2; }
}
And i set the shadow in Test-class:
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, shadows={MyShadowClass.class})
public class MyTest {
#Test
public void testShadow() {
assertEquals(2, MyClass.regularMethod());
}
}
But the shadow is not used.
java.lang.AssertionError:
Expected :2
Actual :1
How to make any custom shadow visible for RobolectricGradleTestRunner?
I have already tried:
http://www.codinguser.com/2015/06/how-to-create-shadow-classes-in-robolectric-3/
https://github.com/jiahaoliuliu/RobolectricSample/blob/master/app-tests/src/main/java/com/jiahaoliuliu/robolectricsample/RobolectricGradleTestRunner.java
Mock native method with a Robolectric Custom shadow class
but i get various compilation errors, such as
InstrumentingClassLoaderConfig not found
Setup not found
how to use custom shadows correctly in robolectric 3.0?
Custom shadows should be avoided and must be a last ditch resort. It should only be used if you cannot do much refactor in your code which is preventing you from running your tests like a native method call. It's better to mock the object of that class or spy using powermock or mockito than custom shadow it. If it's a static method, then use powermock.
In our project, we had a class which had some native methods and it was the config class used everywhere in the app. So we moved the native methods to another class and shadowed that. Those native methods were failing the test cases.
Anyways here's how you can custom shadow in robolectric 3.0:
Create a custom test runner that extends RobolectricGradleTestRunner:
public class CustomRobolectricTestRunner extends RobolectricGradleTestRunner {
public CustomRobolectricTestRunner(Class<?> klass) throws InitializationError {
super(klass);
}
public InstrumentationConfiguration createClassLoaderConfig() {
InstrumentationConfiguration.Builder builder = InstrumentationConfiguration.newBuilder();
builder.addInstrumentedPackage("com.yourClassPackage");
return builder.build();
}
Make sure that that the package doesn't contain any test cases that you are running using robolectric.
I am Jiahao, the creator of the second repository that you are referring.
First of all thanks for to check my code. I do many researches on Android and I am glad that my research is useful for someone else.
Then, the Shadow about Robolectric. I am using Robolectric 3.1 in this project, to test how Robolectric 3 works with MarshMallow:
https://github.com/jiahaoliuliu/robolectricForMarshmallow
I have been testing the new Runtime Permission Manager, as well as shadowing application and activities.
Here is sample code of the shadowed activity:
import android.content.Context;
import com.jiahaoliuliu.robolectricformarshmallow.controller.MainController;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
/**
* Created by Jiahao on 7/18/16.
*/
#Implements(MainController.class)
public class MainControllerShadow {
public void __constructor__ (Context context) {
// Not do anything
}
#Implementation
public String getTextToDisplay(boolean permissionGranted) {
return "Test";
}
}
https://github.com/jiahaoliuliu/robolectricForMarshmallow/blob/master/app/src/test/java/com/jiahaoliuliu/robolectricformarshmallow/shadow/MainControllerShadow.java
And this is how I am using it in the unit test:
package com.jiahaoliuliu.robolectricformarshmallow;
import com.jiahaoliuliu.robolectricformarshmallow.shadow.MainControllerShadow;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.annotation.Config;
import static org.junit.Assert.*;
/**
* Created by Jiahao on 6/30/16.
*/
#RunWith(RobolectricGradleTestRunner.class)
#Config(constants = BuildConfig.class, manifest = Config.NONE, application = FoolApplication.class,
shadows = { MainControllerShadow.class}, sdk = 18)
public class MainActivityTest {
private MainActivity mMainActivity;
#Before
public void setUp() throws Exception {
mMainActivity = Robolectric.setupActivity(MainActivity.class);
}
#After
public void tearDown() throws Exception {
}
#Test
public void testOnCreate() throws Exception {
// Simple test to know that it works
assertTrue(true);
}
}
https://github.com/jiahaoliuliu/robolectricForMarshmallow/blob/master/app/src/test/java/com/jiahaoliuliu/robolectricformarshmallow/MainActivityTest.java
As you can see, I am not using customized Gradle Test Runner. I have checked the source code of Robolectric, for version 3.0 and 3.1 (latest) it is good enough to just specify the shadow classes in the header.
I hope it helps

Android Espresso Tests for phone and tablet

My setup:
- Android App with Phone and Tablet Version
- I am using Android Espresso for UI-Tests (now only for phone version, with phone at buildagent)
What I want to do:
- Now I want Espresso to distinguish between tests for phone and tablet
- So Test A should be only execute by a tablet, Test B should only be executed by a phone and Test C both
- Tests should be executable via gradle task
Three options, all of which are executable via gradlew connectedAndroidTest or custom gradle tasks:
1. Use org.junit.Assume
From Assumptions with assume - junit-team/junit Wiki - Github:
The default JUnit runner treats tests with failing assumptions as ignored. Custom runners may behave differently.
Unfortunately, the android.support.test.runner.AndroidJUnit4 (com.android.support.test:runner:0.2) runner treats failing assumptions as failed tests.
Once this behavior is fixed, the following would work (see Option 3 below for isScreenSw600dp() source):
Phone only: all test methods in the class
#Before
public void setUp() throws Exception {
assumeTrue(!isScreenSw600dp());
// other setup
}
Specific test methods
#Test
public void testA() {
assumeTrue(!isScreenSw600dp());
// test for phone only
}
#Test
public void testB() {
assumeTrue(isScreenSw600dp());
// test for tablet only
}
2. Use a custom JUnit Rule
From A JUnit Rule to Conditionally Ignore Tests:
This led us to creating a ConditionalIgnore annotation and a corresponding rule to hook it into the JUnit runtime. The thing is simple and best explained with an example:
public class SomeTest {
#Rule
public ConditionalIgnoreRule rule = new ConditionalIgnoreRule();
#Test
#ConditionalIgnore( condition = NotRunningOnWindows.class )
public void testFocus() {
// ...
}
}
public class NotRunningOnWindows implements IgnoreCondition {
public boolean isSatisfied() {
return !System.getProperty( "os.name" ).startsWith( "Windows" );
}
}
ConditionalIgnoreRule code here: JUnit rule to conditionally ignore test cases.
This approach can be easily modified to implement the isScreenSw600dp() method in Option 3 below.
3. Use conditionals in the test methods
This is the least elegant option, particularly because entirely skipped tests will be reported as passed, but it's very easy to implement. Here's a full sample test class to get you started:
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
import android.util.DisplayMetrics;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
#RunWith(AndroidJUnit4.class)
public class DeleteMeTest extends ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity mActivity;
private boolean mIsScreenSw600dp;
public DeleteMeTest() {
super(MainActivity.class);
}
#Before
public void setUp() throws Exception {
injectInstrumentation(InstrumentationRegistry.getInstrumentation());
setActivityInitialTouchMode(false);
mActivity = this.getActivity();
mIsScreenSw600dp = isScreenSw600dp();
}
#After
public void tearDown() throws Exception {
mActivity.finish();
}
#Test
public void testPreconditions() {
onView(withId(R.id.your_view_here))
.check(matches(isDisplayed()));
}
#Test
public void testA() {
if (!mIsScreenSw600dp) {
// test for phone only
}
}
#Test
public void testB() {
if (mIsScreenSw600dp) {
// test for tablet only
}
}
#Test
public void testC() {
if (mIsScreenSw600dp) {
// test for tablet only
} else {
// test for phone only
}
// test for both phone and tablet
}
private boolean isScreenSw600dp() {
DisplayMetrics displayMetrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
float widthDp = displayMetrics.widthPixels / displayMetrics.density;
float heightDp = displayMetrics.heightPixels / displayMetrics.density;
float screenSw = Math.min(widthDp, heightDp);
return screenSw >= 600;
}
}
I know the question is bit old but thought to post as this is kinda simpler way.
So just put a Boolean value for targeted screen size -
for ex values-sw600dp.xml for tablet and values.xml for phone.
Put a Boolean value in both
for example in values.xml -
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="tablet">false</bool>
</resources>
and in values-sw600.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<bool name="tablet">true</bool>
</resources>
Then in Test class use this to get resource value-
Context targetContext = InstrumentationRegistry.getTargetContext();
targetContext.getResources().getBoolean(R.bool.tablet);
Boolean isTabletUsed = targetContext.getResources().getBoolean(R.bool.tablet);

Android and JUnit: How to add individual tests to test suite?

I've been struggling for a while with this. When creating a test suite in JUnit/Android, I can to the following:
Add all the tests (in all the classes) that exist in the same package as the suite
Add a specific class contatining testMethods
However, I'm completely unable to do the following:
Add a specific testMethod from a specific class to the test suite.
Now, I understand that this SHOULD be possible, as there are countless examples showing this.
This is how it's supposed to work:
The test class contatining the test methods:
import com.frank.android.lookup.SomeClass;
import android.test.AndroidTestCase;
public class ArithmeticsTests extends AndroidTestCase {
SomeClass sctest;
protected void setUp () throws Exception {
sctest = new SomeClass();
super.setUp();
}
public void testAddNumbers () {
assertEquals(9, sctest.addNumbers(3, 6));
}
public void testSubtractNumbers () {
assertEquals(2, sctest.subtractNumbers(6, 4));
}
protected void tearDown () throws Exception {
super.tearDown();
}
}
And here's the test suite class:
import junit.framework.TestSuite;
public class ProjectTestSuite_SomeTests extends TestSuite {
public static Test suite () {
TestSuite suite = new TestSuite("ArithmeticsTests");
suite.addTest(new ArithmeticsTests("testAddNumbers"));
suite.addTest(new ArithmeticsTests2("testSubtractNumbers"));
return suite;
}
}
Now, the two lines where I try adding the individual test methods result in this error:
The constructor ArithmeticsTests(String) is undefined
Now, I've looke around for a long time, and I cannot find any explanation for this. It seems that something is missing, since it doesn't understand what I'm trying to do. The "string" it complains about is in fact the name of the method - I'm not trying to pass a string to a constructor of the class - I'm trying to add the method of the class to the test suite.
I'm using the JUnit version that's included with the Android SDK here, and I haven't installed anything else related to that. Is there something missing? (Obviously there is, bit what?)
EDIT:
I added a construtor to the ArithmeticsTests class:
public ArithmeticsTests (String s) {}
Now the above error is gone.
However, when I run the test suite, I get this error:
testSuiteCreationFailed
....
Caused by: java.lang.NullPointerException: Method name must not be null.
I came up with the same problem and discovered that, while AndroidTestCase does not have a constructor taking a String parameter, it does have a setName(String name) method. By calling the setName method, you can add individual method to the test case.
Using the code in your example, your test suite may look like:
import junit.framework.TestSuite;
public class ProjectTestSuite_SomeTests extends TestSuite {
public static Test suite () {
TestSuite suite = new TestSuite("ArithmeticsTests");
ArithmeticsTests arithmeticsTests = new ArithmeticsTests();
arithmeticsTests.setName("testAddNumbers");
suite.addTest(arithmeticsTests);
ArithmeticsTests2 arithmeticsTest2 = new ArithmeticsTests2();
arithmeticsTest2.setName("testSubtractNumbers");
suite.addTest(arithmeticsTest2);
return suite;
}
}
This worked for me.
public static TestSuite suite() {
final TestSuite t = new TestSuite();
t.addTest(TestSuite.createTest(TestExampleClass1.class, "test1"));
t.addTest(TestSuite.createTest(TestExampleClass2.class, "test2"));
t.addTest(TestSuite.createTest(TestExampleClass3.class, "test2"));
return t;
}

Categories

Resources