Android Espresso Tests for phone and tablet - android

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);

Related

Android ArraySet in unit tests is not mocked

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.
}

How to run unit test + Espresso repeatedly?

I need to collect some data on my current app in order to analyse performance speed by checking the average ellapsed time during Activity start up. I would like to run a test battery where the activity is started 10, 100, 1000 and 5000 times. For each test, it should remain open for at least 10 seconds (time needed to collect all data that happens asynchronously). What I want is exactly this behaviour without having to write these many methods:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class TestStreamLoadingPerformance {
private static final long TIME_OUT = 2;
private static final long WAITING_TIME = 10000;
#Rule
public ActivityTestRule mActivityRule = new ActivityTestRule(HomepageActivity.class);
private ElapsedTimeIdlingResource mIdleRes = new ElapsedTimeIdlingResource(WAITING_TIME);
#Before
public void setUp() {
IdlingPolicies.setMasterPolicyTimeout(TIME_OUT, TimeUnit.HOURS);
IdlingPolicies.setIdlingResourceTimeout(TIME_OUT, TimeUnit.HOURS);
Espresso.registerIdlingResources(mIdleRes);
}
#After
public void tearDown() {
Espresso.unregisterIdlingResources(mIdleRes);
}
#Test
public void test01() {
}
#Test
public void test02() {
}
#Test
public void test03() {
}
#Test
public void test04() {
}
#Test
public void test05() {
}
#Test
public void test06() {
}
#Test
public void test07() {
}
#Test
public void test08() {
}
#Test
public void test09() {
}
}
With the help of #Be_negative comments, this blog post and this answer, I was able to solve the problem with the code below:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class TestStreamLoadingPerformance {
#Rule
public ActivityTestRule mActivityRule = new ActivityTestRule(HomepageActivity.class, false, false);
#Rule
public RepeatRule mRepeatRule = new RepeatRule();
#After
public void tearDown() {
closeActivity();
}
private void closeActivity() {
final int N = 10;
try {
for (int i = 0; i < N; i++) {
Espresso.pressBack();
}
} catch (NoActivityResumedException e) {
Log.e(TestStreamLoadingPerformance.class.getSimpleName(), "Unable to close activities", e);
}
}
#Test
#RepeatRule.Repeat(times = 10)
public void collectData() {
mActivityRule.launchActivity(null);
}
}
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatRule implements TestRule {
#Retention(RetentionPolicy.RUNTIME)
#Target({
java.lang.annotation.ElementType.METHOD
})
public #interface Repeat {
public abstract int times();
}
private static class RepeatStatement extends Statement {
private final int times;
private final Statement statement;
private RepeatStatement(int times, Statement statement) {
this.times = times;
this.statement = statement;
}
#Override
public void evaluate() throws Throwable {
for (int i = 0; i < times; i++) {
statement.evaluate();
}
}
}
#Override
public Statement apply(Statement statement, Description description) {
Statement result = statement;
Repeat repeat = description.getAnnotation(Repeat.class);
if (repeat != null) {
int times = repeat.times();
result = new RepeatStatement(times, statement);
}
return result;
}
}
The easiest (as in least amount of new code required) way to do this is to run the test as a parametrized test (annotate with an #RunWith(Parameterized.class) and add a method to provide 10 empty parameters). That way the framework will run the test 10 times.
This test would need to be the only test in the class, or better put all test methods should need to be run 10 times in the class.
Here is an example:
#RunWith(Parameterized.class)
public class RunTenTimes {
#Parameterized.Parameters
public static List<Object[]> data() {
return Arrays.asList(new Object[10][0]);
}
public RunTenTimes() {
}
#Test
public void runsTenTimes() {
System.out.println("run");
}
}
With the above, it is possible to even do it with a parameter-less constructor, but I'm not sure if the framework authors intended that, or if that will break in the future.
If you are implementing your own runner, then you could have the runner run the test 10 times. If you are using a third party runner, then with 4.7, you can use the new #Rule annotation and implement the MethodRule interface so that it takes the statement and executes it 10 times in a for loop. The current disadvantage of this approach is that #Before and #After get run only once. This will likely change in the next version of JUnit (the #Before will run after the #Rule), but regardless you will be acting on the same instance of the object (something that isn't true of the Parameterized runner). This assumes that whatever runner you are running the class with correctly recognizes the #Rule annotations. That is only the case if it is delegating to the JUnit runners.
If you are running with a custom runner that does not recognize the #Rule annotation, then you are really stuck with having to write your own runner that delegates appropriately to that Runner and runs it 10 times.
Note that there are other ways to potentially solve this (such as the Theories runner) but they all require a runner. Unfortunately JUnit does not currently support layers of runners. That is a runner that chains other runners.
I had a very similar issue and as a result I've created a library to run Android UI tests multiple times. Might be useful in your case: https://github.com/stepstone-tech/AndroidTestXRunner

Can't get JUnit tests to fail in Android Studio

I'm trying out Android development, but haven't come too far because I'm unable to get a test case to fail.
I have the following test case in the androidTest folder:
package com.example.aaronf.myapplication;
import android.test.*;
public class ToDoListTest extends AndroidTestCase {
private void newToDoListHasNoItems() {
assertEquals(new ToDoList().length, 0);
}
private void addingToDoGivesLengthOfOne() {
ToDoList toDoList = new ToDoList();
toDoList.add(new ToDo());
assertEquals(toDoList.length, 1);
}
public void runTests() {
newToDoListHasNoItems();
addingToDoGivesLengthOfOne();
}
public ToDoListTest() {
super();
runTests();
}
}
The ToDoList class looks like:
package com.example.aaronf.myapplication;
public class ToDoList {
public int length = 0;
public void add(ToDo toDo) {
}
}
It seems like it should fail on addingToDoGivesLengthOfOne(), but I get a green bar.
EDIT
I should add that adding #Test annotations to the methods generates a symbol not found error.
EDIT
I visited the suggested post My Junit test doesn't run. However, there is a difference with my problem. My methods used to have the test prefix, but this didn't affect the outcome. Also, the #Test annotation, as I mentioned before, is flagged with an error: "Cannot resolve symbol Test".
The problem was that my Test Artifact was set to Android Instrumentation Tests instead of Unit Tests. Since my unit tests were being added to the Android Instrumentation group, the unit testing stuff wasn't being recognized.

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

Cucumber-jvm on android: running failed: No test results > Empty test suite

I am developing a test for an Android app using cucumber-jvm. I wrote a feature and the corresponding steps. The console says that there is any test.
Did you have already this problem?
I don't know exactly what I'm doing wrong.
Running tests Test running startedTest running failed: No test results
Empty test suite.
"StepsDefinitions.java"
#CucumberOptions(features = "features")
public class StepsDefinitions extends ActivityInstrumentationTestCase2<LoginActivity> {
public StepsDefinitions() {
super(LoginActivity.class);
assertNotNull(getActivity());
Log.i("That","It is running.");
}
#Given("^I have a UserBox$")
public void I_am_on_the_Login_Screen() {
EditText etLoginUser = (EditText) getActivity().findViewById(R.id.User);
assertNotNull(etLoginUser);
}
#Then("^I should see on the display$")
public void I_should_see_s_on_the_display() {
EditText display = (EditText) getActivity().findViewById(R.id.Pass);
} }
Do you have the Runner class? You just need a small 'marker' class to tell JUnit to invoke the Cucumber test suite (put it in the same package as your StepDefinitions).
import cucumber.api.junit.Cucumber;
import org.junit.runner.RunWith;
#RunWith(Cucumber.class)
public class RunCukesTest {
// No further code needed
}

Categories

Resources