Unable to find resource ID when building fragment with Robolectric - android

Using Robolectric 3.3.2 I am trying to build a fragment and attach it to an activity. I tried Robolectric.buildFragment(MyFragment.class, MyActivity.class).create() but I get exception android.content.res.Resources$NotFoundException: Unable to find resource ID #0x1 in packages [android, com.my.package.for.android].
I also tried building the activity and fragment separately and attach the fragment using the fragment manager but it seems that the fragment is automatically attached to a dummy activity so I cannot attach it to my activity.
Minimalistic example:
#RunWith(RobolectricTestRunner.class)
#Config(sdk = Build.VERSION_CODES.KITKAT, manifest = "./src/main/AndroidManifest.xml")
public class MyTest {
#Test
public void WorkingTest1() throws Exception {
Robolectric.buildFragment(MyFragment.class).create();
}
#Test
public void WorkingTest2() throws Exception {
Robolectric.buildActivity(MyActivity.class).create();
}
#Test
public void FailingTest() throws Exception {
Robolectric.buildFragment(MyFragment.class, MyActivity.class).create();
}
public static class MyActivity extends Activity {
}
public static class MyFragment extends Fragment {
}
}

I have a root Activity frame already defined for my Activity, so I can supply that for the FragmentManager transaction inside Robolectric's FragmentController:
Robolectric
.buildFragment(MyFragment.class, MyActivity.class)
.create(R.id.my_activity_frame);
In my Activity:
setContentView(R.id.my_activity_frame);

Upon further investigation I did find a workaround (albeit ugly). It seems Robolectric expects a ViewGroup with id 1 in the activity view hierarchy. The solution I found was to write findViewById(android.R.id.content).setId(1); in the activity's onCreate(Bundle) (or add a new ViewGroup and set it's id to 1 programmatically). This seems like a terrible thing to to in production code though so I made a test subclass.
public class MyTestableActivity extends MyActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findViewById(android.R.id.content).setId(1);
}
}

If you are running with default RobolectricTestRunner , make sure your res/ folder is at the same location as AndroidManifest.xml.
I got the issue resolved with something like
#RunWith(RobolectricTestRunner.class)
#Config(manifest = "./path/AndroidManifest.xml")
public class FragmentTest{
}

I resolved this by doing a variation of other answers:
Robolectric
.buildFragment(YourFragment.class, Activity.class)
.create(android.R.id.content, new Bundle());
Note that you can also substitute Activity.class for a static inner test-only YourTestActivity.class as well.

Related

Android execution functional test order

I have an application where the MainActivity layout is made of two TextView (tvName and tvEmail) and one button (btnLogout).
I have a functional test (code at the end) with these test methods:
testUserDetailsAreShown: checks if the TextView contain the correct text values.
testLoginActivityStarts: performs a click action on btnLogout button and checks if the LoginActivity is loaded.
My problem is that if I comment the testLoginActivityStarts, then testUserDetailsAreShown pass. However, when both are uncommented then both fail.
java.lang.NullPointerException
at android.support.test.espresso.intent.Intents.resumedActivitiesExist(Intents.java:235)
at android.support.test.espresso.intent.Intents.intended(Intents.java:184)
at android.support.test.espresso.intent.Intents.intended(Intents.java:169)
at net.example.login.MainActivityTest.testLoginActivityStarts(MainActivityTest.java:46)
and
android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: net.example.login:id/tvName
I think that this is because testLoginActivityStarts is executed before testUserDetailsAreShown and when testUserDetailsAreShown is under execution, the loaded activity is not MainActivity, but LoginActivity.
Can anyone help me with this? I need to make both tests pass. I'm sure that they should pass.
Here the test code:
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private String USER_NAME = "UserName";
private String USER_EMAIL = "UserEmail";
public MainActivityTest(){
super(MainActivity.class);
}
public void testUserDetailsAreShown() {
onView(withId(R.id.tvName)).check(matches(withText(USER_NAME)));
onView(withId(R.id.tvEmail)).check(matches(withText(USER_EMAIL)));
}
public void testLoginActivityStarts() {
onView(withId(R.id.btnLogout)).perform(click());
intended(hasComponent(LoginActivity.class.getName()));
}
}
Make sure to call Intents.init() in the setup of your tests. That should fix the NullPointerException you described:
java.lang.NullPointerException
at android.support.test.espresso.intent.Intents.resumedActivitiesExist(Intents.java:235)
at android.support.test.espresso.intent.Intents.intended(Intents.java:184)
at android.support.test.espresso.intent.Intents.intended(Intents.java:169)
at net.example.login.MainActivityTest.testLoginActivityStarts(MainActivityTest.java:46)
For more information see javadoc about Intents.init()

Android: Writing test cases for Fragments

In my previous projects I've done most of the work through Activities and used ActivityInstrumentationTestCase2 as per the document:
http://developer.android.com/tools/testing/activity_testing.html
I have an idea how to work with Activity Test cases; but when it comes to Fragment ,I don't have much idea nor found much documents related to that.
So how to write test cases when I have several fragments with one or two actvities?
Any example code or sample would be more helpful.
Here's a rough guide using ActivityInstrumentationTestCase2:
Step 1. Create a blank Activity to hold your fragment(s)
private static class FragmentUtilActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout view = new LinearLayout(this);
view.setId(1);
setContentView(view);
}
}
Step 2:
Inside your test, instantiate your fragment and add it to the blank activity
public class MyFragmentTest extends ActivityInstrumentationTestCase2<FragmentUtilActivity> {
private MyFragment fragment;
#Before
public void setup() {
fragment = new MyFragment();
getActivity().getFragmentManager().beginTransaction().add(1, fragment, null).commit();
}
}
Step 3 Test your instantiated fragment
#Test
public void aTest() {
fragment.getView().findViewById(...);
}
If you're using robolectric, this is pretty straightforward using the FragmentUtilTest class:
#Test
public void aTest() {
// instantiate your fragment
MyFragment fragment = new MyFragment();
// Add it to a blank activity
FragmentTestUtil.startVisibleFragment(fragment);
// ... call getView().findViewById() on your fragment
}
Here is my working solution:
Create an instrumentation unit test class for this in androidTest directory, i.e.:
public class FragmentTest extends
ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity testingActivity;
private TestFragment testFragment;
//...
}
call this constructor inside this new class:
public FragmentTest() {
super(MainActivity.class);
}
override the setUp() method (be sure to have R.id.fragmentContainer in your Activity class) where you will call at the end waitForIdleSync():
#Override
protected void setUp() throws Exception {
super.setUp();
// Starts the activity under test using
// the default Intent with:
// action = {#link Intent#ACTION_MAIN}
// flags = {#link Intent#FLAG_ACTIVITY_NEW_TASK}
// All other fields are null or empty.
testingActivity = getActivity();
testFragment = new TestFragment();
testingActivity.getFragmentManager().beginTransaction().add(R.id.fragmentContainer,testFragment,null).commit();
/**
* Synchronously wait for the application to be idle. Can not be called
* from the main application thread -- use {#link #start} to execute
* instrumentation in its own thread.
*
* Without waitForIdleSync(); our test would have nulls in fragment references.
*/
getInstrumentation().waitForIdleSync();
}
Write a test method, for example somethng like:
public void testGameFragmentsTextViews() {
String empty = "";
TextView textView = (TextView)testFragment.getView().findViewById(R.id.myTextView);
assertTrue("Empty stuff",(textView.getText().equals(empty)));
}
Run the test.
AndroidX provides a library, FragmentScenario, to create fragments and change their state.
app/build.gradle
dependencies {
def fragment_version = "1.0.0"
// ...
debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'
}
example
#RunWith(AndroidJUnit4::class)
class MyTestSuite {
#Test fun testEventFragment() {
// The "fragmentArgs" and "factory" arguments are optional.
val fragmentArgs = Bundle().apply {
putInt("selectedListItem", 0)
}
val factory = MyFragmentFactory()
val scenario = launchFragmentInContainer<MyFragment>(
fragmentArgs, factory)
onView(withId(R.id.text)).check(matches(withText("Hello World!")))
}
}
More at official docs.
Use your main activity as the test activity that you send to ActivityInstrumentationTestCase2. Then you can work with the fragments through the fragment manager of your main activity that launches the fragments. This is even better than having a test activity because it uses the logic that you write in your main activity to test scenarios, which gives a fuller and more complete test.
Example:
public class YourFragmentTest extends ActivityInstrumentationTestCase2<MainActivity> {
public YourFragmentTest(){
super(MainActivity.class);
}
}
Right now ActivityInstrumentationTestCase2 is deprecated. Now you can use rules in order to use activities within your tests: http://wiebe-elsinga.com/blog/whats-new-in-android-testing/
In order for those to work you'll have to add the dependencies to you build.gradle:
testCompile 'com.android.support.test:rules:0.5'
testCompile 'com.android.support.test:runner:0.5'
(See Why cannot I import AndroidJUnit4 and ActivityTestRule into my unit test class?)

Robolectric: how to test class which use application instance inside?

I want to test a fragment UserConnectFragment which contains a variable PlateformConnect. This class has a method to initialise Facebook SDK:
#Override
public void create() {
FacebookSdk.sdkInitialize(MyApplication.getInstance().getApplicationContext());
}
I extended Android application with MyApplication class.
In UserConnectFragment, I use PlateformConnect like that:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Must be done before the content view assignement!
PlateformConnect.getInstance().create();
...
In my Robolectric class test:
#Before
public void setUp() throws Exception {
// Create basic activity, and add fragment
mActivity = Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();
mUserConnectFragment = new UserConnectFragment();
addMapFragment(mActivity, mUserConnectFragment);
//mLoginButton = (Button) mActivity.findViewById(R.id.facebook_button);
}
There is a crash when this test runs:
java.lang.NullPointerException
at com.xxx.yyyy.ui.intro.UserConnectFragment.onViewCreated(UserConnectFragment.java:77)
And this error appears because I use:
MyApplication.getInstance().getApplicationContext()
... and getInstance() returns null.
In my application I use MyApplication.getInstance() in a lot of class, so how can I do to test with Robolectric ??
Thanks guys!
I found the solution: just add #Config(xxx) to set the Application class name.
#RunWith(RobolectricTestRunner.class)
#Config(application = MyApplication.class)
public class UserConnectFragmentTest {
...
More details here: http://robolectric.org/custom-test-runner/
ApplicationProvider.getApplicationContext() worked for me.

How to determine if Android Application is started with JUnit testing instrumentation?

I need to determine in runtime from code if the application is run under TestInstrumentation.
I could initialize the test environment with some env/system variable, but Eclipse ADK launch configuration would not allow me to do that.
Default Android system properties and environment do not to have any data about it. Moreover, they are identically same, whether the application is started regularly or under test.
This one could be a solution: Is it possible to find out if an Android application runs as part of an instrumentation test but since I do not test activities, all proposed methods there won't work. The ActivityManager.isRunningInTestHarness() method uses this under the hood:
SystemProperties.getBoolean("ro.test_harness")
which always returns false in my case. (To work with the hidden android.os.SystemProperties class I use reflection).
What else can I do to try to determine from inside the application if it's under test?
I have found one hacky solution: out of the application one can try to load a class from the testing package. The appication classloader surprisingly can load classes by name from the testing project if it was run under test. In other case the class is not found.
private static boolean isTestMode() {
boolean result;
try {
application.getClassLoader().loadClass("foo.bar.test.SomeTest");
// alternatively (see the comment below):
// Class.forName("foo.bar.test.SomeTest");
result = true;
} catch (final Exception e) {
result = false;
}
return result;
}
I admit this is not elegant but it works. Will be grateful for the proper solution.
The isTestMode() solution did not work for me on Android Studio 1.2.1.1. Almighty Krzysztof from our company tweaked your method by using:
Class.forName("foo.bar.test.SomeTest");
instead of getClassLoader(). Thanks for Krzysztof!
We created a solution to pass parameters to the MainActivity and use it inside the onCreate method, enabling you to define how the Activity will be created.
In MainActivity class, we created some constants, which could also be an enum. We created a static attribute too.
public class MainActivity {
public static final int APPLICATION_MODE = 5;
public static final int UNIT_TEST_MODE = 10;
public static final int OTHER_MODE = 15;
public static int activityMode = APPLICATION_MODE;
(...)
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
switch (activityMode) {
case OTHER_MODE:
(...)
break;
case UNIT_TEST_MODE:
Log.d(TAG, "Is in Test Mode!");
break;
case APPLICATION_MODE:
(...)
break;
}
(...)
}
(...)
}
We made MainActivityTest class abstract, created a setApplicationMode and called this method inside the setUp() method, before calling the super.setUp() method.
public abstract class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
protected void setUp() throws Exception {
setApplicationMode(); // <=====
super.setUp();
getActivity();
(...)
}
(...)
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.UNIT_TEST_MODE;
}
}
All other test classes inherit from MainActivityTest, if we want it to have another behaviour, we can simply override the setApplicationMode method.
public class OtherMainActivityTest extends MainActivityTest {
(...)
#Override
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.OTHER_MODE;
}
}
The user nathan-almeida is the friend that is co-author of this solution.

Is it possible to test an Abstract activity with Robolectric

I use abstract activity classes in my code to well, abstract away some features from the activity classes.
I'm trying to test the abstract activity classes using Robolectric and the gradle-android-test-plugin using subclasses that extend the abstract class. I can't seem to get it to work though.
Does anyone have any experience in this area and is it even possible ? Basic structure is :
#RunWith(RobolectricGradleTestRunner.class)
public class AbstractActivityTest {
private ActivityTest activity;
#Before
public void setUp() throws Exception {
activity = Robolectric.buildActivity(ActivityTest.class).create().get();
}
private class ActivityTest extends AbstractActivity {
// do something
}
}
Initially, I got the error message the sub class wasn't static so I made it static. Now I get the following two fails:
initializationError FAILED
java.lang.Exception: Test class should have exactly one public constructor
initializationError FAILED
java.lang.Exception: No runnable methods
Any obviously true tests I put in #Test methods succeed.
The first error saying that you added non-default constructor to your test class or changed access level for default one. But as it says junit Test class should have at least one public constructor.
The second one says that at least one method in test class should have #Test annotation (junit 4) or starts with test substring (junit 3).
Yo can doing exactly what you are trying to do: subclass the abstract activity and instance the concrete class.
However, you need to declare the class extending the abstract Activity in it's own public file. If it's a nested class Robolectric will fail to instance it.
I don't know why, though.
I test an abstract activity this way:
1. Creating the abstract avtivity:
public abstract class AbstractActivity extends AppCompatActivity {
public int getNumber() {
return 2;
}
}
2. Creating the test class:
You just need to declare a static nested subclass of your abstract class.
#RunWith(RobolectricTestRunner.class)
public class AbstractActivityTest {
#Test
public void checkNumberReturn() throws Exception {
TestAbstractActivity testAbstractActivity = Robolectric.setupActivity(TestAbstractActivity.class);
assertThat(testAbstractActivity.getNumber(), is(2));
}
public static class TestAbstractActivity extends AbstractActivity {
}
}

Categories

Resources