I'm trying to create a simple Android Activity test that checks that a new Activity is started when a button is clicked. The code:
public class LoginActivityTest extends ActivityInstrumentationTestCase2<LoginActivity> {
public LoginActivityTest() {
super(LoginActivity.class);
}
#Override
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mActivity = getActivity();
mLoginButton = (Button) mActivity.findViewById(R.id.login_button);
mSkipButton = (Button) mActivity.findViewById(R.id.skip_button);
}
{...}
public void testSkipButton() {
Instrumentation.ActivityMonitor monitor =
getInstrumentation().addMonitor(
"com.mycompany.myproject.view.QuestionsActivity", null, false);
mActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
mSkipButton.requestFocus();
mSkipButton.performClick();
}
});
QuestionsActivity nextActivity =
(QuestionsActivity) getInstrumentation().waitForMonitorWithTimeout(monitor, 20);
assertNotNull(nextActivity);
nextActivity.finish();
}
private LoginActivity mActivity;
private Button mLoginButton;
private Button mSkipButton;
}
When I reach waitForMonitorWithTimeout() a NoClassDefFoundException is raised.
It's important to take into account that QuestionsActivity (the activity that should be launched) is a FragmentActivity, not an Activity, but FragmentActivity inherits from Activity, so I really don't understand what's happening there. Maybe InstrumentationTest cannot deal with Fragments or FragmentActivities.
mActivity is the current Activity that it's being tested, and it's a pure Activity.
Please, help!
The issue was that I added a reference to the support library from the testing project. See this link:
FragmentActivity can not be tested via ActivityInstrumentationTestCase2
Now tests work fine :)
Related
How do I assert an activity in Espresso after I click on a view item?
onView(withId(com.example.android.notepad.R.id.XYZ)).perform(click());
You should simulate the process of clicking a button and then test if the activity at the top of the stack is the one you're looking for
#RunWith(AndroidJUnit4.class)
public class YourTestClass{
#Test
public void testButton() {
Espresso.onView(ViewMatchers.withId(R.id.yourButtonId)).perform(ViewActions.click());
Assert.assertEquals(getActivityInstance().getClass(), YourActivityThatShouldStart.class);
}
private Activity getActivityInstance() {
final Activity[] currentActivity = {null};
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity[0] = (Activity) resumedActivities.iterator().next();
}
}
});
return currentActivity[0];
}
}
On the testButton function, there are two lines, the first one to click on your button, the second one is to check the resulting activity
Espresso works on the main thread so your fine
Inside your tests you probably have this:
#Rule
public ActivityTestRule<MyActivity> testRule = new ActivityTestRule<>(MyActivity.class);
Then you can call:
testRule.getActivity()
to obtain instance of Activity.
the just call
testRule.getActivity() instanceof MyActivity
I try to create a Robolectric test (3.0-rc02) for the following Activity:
public class NotificationActivity extends ActionBarActivity {
private NotificationFragment fragment;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notification);
}
}
The test is looking like this:
#Config(manifest = IConfig.MANIFEST_PATH, emulateSdk = IConfig.SDK_VERSION, reportSdk = IConfig.SDK_VERSION)
#RunWith(RobolectricTestRunner.class)
public class AbstractFragmentTest {
#Test
public void test() {
Robolectric.buildActivity(NotificationActivity.class).setup().visible();
}
where SDK_VERSION = 18.
When running the test, I get this error:
java.lang.NullPointerException
at org.robolectric.res.builder.DefaultPackageManager.getActivityInfo(DefaultPackageManager.java:164)
at org.robolectric.util.ActivityController.getActivityInfo(ActivityController.java:65)
at org.robolectric.util.ActivityController.attach(ActivityController.java:51)
at org.robolectric.util.ActivityController$1.run(ActivityController.java:121)
at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:309)
at org.robolectric.shadows.CoreShadowsAdapter$2.runPaused(CoreShadowsAdapter.java:47)
at org.robolectric.util.ActivityController.create(ActivityController.java:118)
at org.robolectric.util.ActivityController.create(ActivityController.java:129)
at org.robolectric.util.ActivityController.setup(ActivityController.java:210)
at com.viae.common.view.AbstractFragmentTest.test(AbstractFragmentTest.java:31)
Anyone knows what I'm doing wrong over here?
Issue solved
Using FragmentActivity in stead of ActionBarActivity did the trick to me.
My main logic is in a fragment (DialogFragment) inside the activity. Both activities are supporting the DialogFragment, so for my functional tests it doesn't matter which of the 2 parent activities I use.
ActionBarActivity is deprecated. Use AppCompatActivity instead
First, I am an android rookie, so my solution ways can be found awkward, and i am open to suggestions.
I am trying to create a game manager object that handles all transitions between activities. And my purpose is that while in an activity, menuOut method will call the changeActivity method of GameManager object with nextActivity argument and changeActivity will start that Activity. I am getting errors consistently, and did not find a solution.
Here is my source codes:
GameManager:
public class GameManager{
public SplashScreen splash = new SplashScreen();
public MainScreen main = new MainScreen();
public LoadingScreen load = new LoadingScreen();
Context tempContext;
public GameManager(Context base) {
super();
tempContext = base;
}
public void start(){
createScreens();
Intent intent = new Intent(tempContext, splash.getClass());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
tempContext.startActivity(intent);
}
public void createScreens() {
//here is the method that i am trying to find a solution
((SplashScreen)splash.getContext()).setGameClass(this);
((MainScreen)main.getContext()).setGameClass(this);
((LoadingScreen)load.getContext()).setGameClass(this);
}
public void changeMenu(MenuType nextMenu, MenuType previousMenu){
Intent intent2;
switch(nextMenu){
case MAIN_SC:
tempContext = main.getContext();
intent2.setClass(tempContext, main.getClass());
intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
tempContext.startActivity(intent2);
case GAME_LOADING_SC:
tempContext = load.getContext();
intent2.setClass(tempContext, load.getClass());
intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
tempContext.startActivity(intent2);
default:
break;
}
}
}
And here is SplashScreen activity:
public class SplashScreen extends Activity {
public Context context = this;
public GameManager gameman;
private static final int SPLASH_DURATION = 4000;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
splash();
menuOut();
}
public Context getContext() {
return this;
}
public void splash() {
LinearLayout ll = new LinearLayout(this);
ll.setOrientation(LinearLayout.HORIZONTAL);
ll.setBackgroundResource(R.drawable.game_loop_splash);
setContentView(ll);
Handler handler = new Handler();
// run a thread after 2 seconds to start the home screen
handler.postDelayed(new Runnable() {
#Override
public void run() {
finish();
}
}, SPLASH_DURATION);
}
public void setGameClass(GameManager game){
gameman = game;
}
private void menuOut(){
gameman.changeMenu(MenuType.GAME_LOADING_SC, MenuType.GAME_SPLASH_SC);
this.onDestroy();
}
}
I can not return to the GameManager and call the changeMenu method.
I am very exhausted to get null pointer exceptions.
Any idea?
From what I read, you are trying to implement a singleton pattern. There are two ways I'd recommend to do that on android:
Extend the Application class, register your class in the manifest and use getApplication() in your activities to get access to it:
// In MyApplicationSubclass.java:
public final class MyApplicationSubclass extends Application {
/* ... */
public void myMethod() {
// insert some code here
}
/* ... */
}
// From your Activity:
((MyApplicationSubclass) this.getApplication()).myMethod();
Use a "normal" java singleton pattern, e.g. use a private constructor and keep one static instance within your GameManager class (this is the way the Android docs recommend, but I personally prefer the first way when having something that is logically bound to the Application).
Also, if you're only using your central class to do static stuff, you can just mark all its method as static and access them directly. Transfer Context objects as parameters to these methods, and you should be able to start activities without any static variables (which are sometimes hard to implement properly in Android, as your VM might get restarted from time to time).
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.
I've got one question regarding the intent action ACTION_CALL.
What is the correct way of getting back to the own application/activity after the user ends the call?
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:" +m1));
startActivity(intent);
I have made the call using the above code. Please provide me with a solution to call my own activity after the call action.
unfortunately some phones have settings to force going into for example the Call Log after a call...
However, you can run a loop after your startActivity to check TelephonyManager.getCallState, and when it's again TelephonyManager.CALL_STATE_IDLE, you can restart your own Activity
be sure to add some sleep to the loop
AFter the endofcall.......it just had to come back to the activity..!! you can handle that one onRestart();
I run with the same problem, and ended up solving it like this:
Make a Callback interface with single (or multiple if you want)
methods
Implement that interface in your activity
Make a reference to that interface inside PhoneStateListener class
Call a method within that interface when the call ended
public class CallTracker extends PhoneStateListener implements Runnable {
private Context mActivity;
private Callback mCallback;
public interface Callback {
void onCallEnded();
}
public CallTracker(Activity activity) {
super();
mActivity = activity;
if (mActivity instanceof Callback) {
mCallback = (Callback) mActivity;
}
}
#Override public void onCallStateChanged(int state, String incomingNumber) {
if (state == TelephonyManager.CALL_STATE_IDLE) {
mCallback.onCallEnded();
}
}
public class CallerActivity extends AppCompatActivity implements
CallTracker.Callback {
#Override public void onCallEnded() {
Toast.makeText(this, "Call ended!", Toast.LENGTH_SHORT).show();
}
}