Testing Android Components With Espresso - android

I have a number of custom Android components and wish to test them using Espresso. As an Espresso test runs against an Activity I added a simple Activity class to the androidTest directory which programatically creates a view with my component in it ready for testing.
For example if I'm testing a MyView component then my Espresso test class might look something like this:
public class MyViewTest extends ActivityInstrumentationTestCase2<MyViewTestActivity>
{
private MyViewTestActivity activity;
public MyViewTest()
{
super(MyViewTestActivity.class);
}
#Override
protected void setUp() throws Exception
{
super.setUp();
setActivityInitialTouchMode(false);
// Launches the activity
activity = getActivity();
}
// Ensure that expected items are present
public void testLayout()
{
onView(withId(activity.view.getId())).check(matches(isDisplayed()));
}
}
with a simple MyViewTestActivity as follows:
public class MyViewTestActivity extends Activity
{
private static final Random RANDOM = new Random();
public LinearLayout layout;
public MyView view;
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
layout = new LinearLayout(this);
layout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
view = new MyView(this);
view.setId(RANDOM.nextInt());
view.setItem("Test text");
layout.addView(view);
setContentView(layout);
}
}
My problem is I appear to need to add the test Activity class, in this case MyViewTestActivity, to the main AndroidManifest.xml to make this work, otherwise I receive an Unable to resolve activity for: Intent... error when attempting to run the test. However I now have test activities in the main manifest, which seems like a bad thing to do.
How can I set up test-specific activities which are included in the test manifest but not the main one?
I'm using the gradle-based build system for Android.

Yes, you have to add MyViewTestActivity to the main AndroidManifest.xml.
If you look at the ActivityInstrumentationTestCase2 source code, you will see that getActivity() looks for the tested Activity in the target (i.e., the app under test) context.
Here is the relevant part of the source code.
#Override
public T getActivity() {
// ...
final String targetPackage = getInstrumentation().getTargetContext().getPackageName();
// ...
a = launchActivity(targetPackage, mActivityClass, null);
// ...
setActivity(a);
// ...
}
What I do in my projects is that I create a generic TestingActivity, put it in a .test package in the target app, and use it for all GUI-component testing. It is not ideal, but I never had any problem with this approach.

Related

Android layout coding on Java: declaration location

I was trying out coding android layout on Java. Then, I noticed that following code is incorrect (as in not working):
public class MainActivity extends Activity {
LinearLayout topLayout = new LinearLayout(this);
Button button1 = new Button(this);
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
... setText, layoutParam and etc ...
topLayout.addView(button1, layoutParam);
setContentView(topLayout);
}
}
while following code is correct (working):
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
LinearLayout topLayout = new LinearLayout(this);
Button button = new Button(this);
... setText, layoutParm and etc ...
topLayout.addView(button1, layoutParam);
setContentView(topLayout);
}
}
Why is that?
The context of an Activity is created once onCreate() is called.
In the first case, you don't have context set.
The comment made by #Blundell explains it, but if you want to go a little bit deeper, you need to understand the object lifecycle and order of things.
An Activity is a Java Object in the end. And so it must go through the Java Object creation process.
public class MainActivity extends Activity {
LinearLayout topLayout = new LinearLayout(this);
Button button1 = new Button(this);
Member variables (or Fields) are created right after the constructor. If you don't have a constructor, one is created for you (even tho you don't see it) you can try to create one:
public class MainActivity extends Activity {
public MainActivity() {
super();
Log.d("MAINACTIVITY", "Constructor!");
}
Now add log to your onCreate()…
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("MAINACTIVITY", "onCreate!");
}
Start your Activity and look at the log.
You should see:
Constructor!
onCreate!
With that being said, Since an Activity implements a Context interface, you cannot use them until they are not constructed, something that can be considered done after the constructor is called and ends.
Field members which are initialized inline, must be initialized during object Construction too, because that's the idea of their initialization being there…
So in short, you're trying to initialize Field Members that require something (a context) that is not yet available because it hasn't been created (this).
;)

Running Android ActivityUnitTestCase results in "RuntimeException: could not find test class"

Trying to generate a simple ActivityUnitTestCase to test various Fragments in my Android code.
public class MenuFragmentTest extends ActivityUnitTestCase<FragmentAdapter> {
static {
FragmentAdapter.setFragmentUnderTest(new MenuFragment());
}
public MenuFragmentTest() {
super(FragmentAdapter.class);
}
public void testMenuOptions() {
System.out.println(getActivity().findViewById(1));
}
}
The generalized FragmentAdapter that I'm trying to use to test Fragments in the app:
public class FragmentAdapter extends FragmentActivity {
static Fragment fragmentUnderTest;
public static void setFragmentUnderTest(Fragment fragmentUnderTest) {
FragmentAdapter.fragmentUnderTest = fragmentUnderTest;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout ll = new LinearLayout(this);
ll.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
setContentView(ll);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragmentUnderTest, null);
fragmentTransaction.commit();
}
}
After the main app installs successfully, the Android JUnit Test Runner fails with
java.lang.RuntimeException: Could not find test class. Class: com.XXX.core.MenuFragment
Why can the Test Runner not find the test class I am running?
Apparently the name of the test package really does matter:
From http://developer.android.com/tools/testing/testing_android.html:
If the application under test has a package name of com.mydomain.myapp, then the Android tools set the test package name to com.mydomain.myapp.test
Altering the package instrumentation targetPackage attribute is not good enough to allow you to create tests in the same package as the activity under test, even if the test is in the separate test project.

ActivityMock for Fragment in test project

I have Activity that contains few Fragments, now I would like to test one of this Fragment but I would like to separate test and test only core functionality of selected Fragment not bothering what is happening in main Activity.
My idea is to create a mock Activity which will just add Fragment in onCreate() method. Then I will make some tests. But I would not like to include mock Activity to my main project, I would rather include it to test project. So I did something like this:
I have created MockActivity:
public final class ActivityMock extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentTransaction t = getFragmentManager().beginTransaction();
MyFragment f = new MyFragment();
t.add(f, "MY_FRAGMENT");
t.commit();
}
}
I want to test it like this:
public final class MyFragmentTest extends
ActivityInstrumentationTestCase2<ActivityMock> {
public MyFragmentTest() {
super(ActivityMock.class);
}
public void testSomething() {
ActivityMock mActivity = getActivity();
//do some assertions
}
}
The problem is that I I get error:
java.lang.RuntimeException: Unable to resolve activity for: Intent {
act=android.intent.action.MAIN flg=0x10000000
cmp=com.example/.test.ActivityMock }
Ok next I tried to modify test project AndroidManifest.xml
<activity android:name=".ActivityMock" />
But I got same error. I think that is because anyway during test run main project is searched for ActivityMock. So I tried to add
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.example.test" />
I don know if it is a good idea, but main thought is that test project will be able to test (instrument) itself. But now I get:
junit.framework.AssertionFailedError: Exception in constructor: testSomething
(java.lang.NoClassDefFoundError: com.example.test.ActivityMock
So I think that modified AndroidManifest.xml worked but still ActivityMock class is being searched in main project, though it is in test project.
I assume that getActivity() method always look for activity class in main project.
Does anybody tried to test Fragment this way and was able to create Activity mock?
Cheers

Is Robolectric able to assert that methods have been invoked?

I've got a button defined in my layout as follows :
<Button
android:id="#+id/speakButton"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="#string/speak"
android:onClick="speak"
/>
The onClick binds to a method in my activity, such as :
public void speak(View v)
{
// Do my stuff here
}
Using Robolectric, I'm able to create a simple test class for that activity, I'd like to know if its possible that I could have a test that invokes the button, and ensures the method in my activity was invoked OK.
(I've got a whole bunch of buttons throughout my app, so intending to have tests to ensure they are wired up correctly, unless anyone has any suggestions as to why I shoudln't bother)
#RunWith(RobolectricTestRunner.class)
public class MyActivityTest
{
private MyActivitymActivity;
private Button speakButton;
#Before
public void setUp() throws Exception
{
mActivity = new MyActivity();
mActivity.onCreate(null);
speakButton = (Button) mActivity.findViewById(com.jameselsey.apps.androidsam.R.id.speakButton);
}
#Test
public void testButtonsVisible()
{
assertThat(speakButton.getVisibility(), equalTo(View.VISIBLE));
}
#Test
public void buttonsInvokeIntendedMethods()
{
// Unsure how to implement this test
}
}
I've never used them but I believe you can do this with the TouchUtils class. Here is a link to the Android TouchUtils docs. In particular you should look at the clickView method.

easy android unit test fail

I have a simple activity with only one edittext which was set to "http://" in xml. Based on google tutorial, I wrote some unit tests for status check. I want to practice unit test and pass all tests. But I cannot pass testStateDestroy() and testStatePause() (log shows mUrlView=="changed"). The code below is very easy, did I miss something? Thank you in advance.
public class MainActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private Activity mActivity;
private EditText mUrlView;
public MainActivityTest() {
super("au.com.crystalfish.safeshare.activity", MainActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
mActivity = this.getActivity();
mUrlView = (EditText) mActivity.findViewById(au.com.crystalfish.safeshare.R.id.url);
}
public void testPreconditions() {
assertNotNull(mActivity);
assertEquals(mUrlView.getText().toString(), "http://");
}
#UiThreadTest
public void testRotate() {
mUrlView.setText("changed");
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
assertEquals(mUrlView.getText().toString(), "changed");
}
#UiThreadTest
public void testStateDestroy() {
mUrlView.setText("changed");
assertEquals(mUrlView.getText().toString(), "changed");
mActivity.finish();
mActivity = this.getActivity();
assertEquals(mUrlView.getText().toString(), "http://"); <===========should be "http://" since it is a new activity
}
#UiThreadTest
public void testStatePause() {
Instrumentation mInstr = this.getInstrumentation();
mInstr.callActivityOnPause(mActivity);
mUrlView.setText("changed");
mInstr.callActivityOnResume(mActivity);
assertEquals(mUrlView.getText().toString(), "http://");<======should be "http://" since the text should bot be changed when the activity was paused
}
}
Well, for your testStatePause() test I think that may be valid. Even if the activity is paused you are still free to modify it however you want since you still have a reference to it (or one of its textViews at least. In a test like this, you should change the value of the textView in the actual activity's onPause method, then use your testStatePause test to verify that onPause was called and did its job correctly, then do the same for onResume().
I'm not really familiar with Android's testing framework but maybe a similar issue is happening with your testStateDestroy() test. Your mUrlView could still be pointing to the old, finished Activity. The old activity is no longer valid but its widgets might be.

Categories

Resources