Functional Test Android Studio - android

I'm trying to understand how functional tests work in android applications. But I have difficulties.
In the case shown below, I check in database if there are any "work" in the database and if this exists, it goes to the WorkActivity activity, otherwise it goes to CalendarActivity.
MainActivity
public void btnNotes(View view) {
if (userDAO.existWork() == false) {
Intent intentRegisterWork = new Intent(this, WorkActivity.class);
startActivity(intentRegisterWork);
} else {
Intent intent = new Intent(this, CalendarActivity.class);
startActivity(intent);
}
MainActivityTest
#RunWith(AndroidJUnit4.class)
public class MainActivityTest {
#Rule
public ActivityTestRule<MainActivity> mMainActivityTestRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
#Test
public void testChangeIntent() {
onView(withId(R.id.btnNotes));
Intent intent = new Intent (mMainActivityTestRule.getActivity(),WorkActivity.class);
mMainActivityTestRule.launchActivity(intent);
}
}
How do I test this?

Related

Cannot go to MainActivity when using LiveData

I have splash screen without a layout file. This is what I have tried:
public class SplashActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!isNotAuthenticated()) {
openLoginInActivity();
} else {
openMainActivity();
}
finish();
}
private void openMainActivity() {
viewModel.idLiveData.observe(this, new Observer<String>() {
#Override
public void onChanged(String id) {
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra("id", id);
startActivity(intent); //Go to MainActivity
}
});
}
}
Using this code, I get this error:
2019-09-03 12:03:06.615 1871-1934/? E/ViewRootImpl[myapp]: Could not unlock surface
java.lang.IllegalArgumentException
at android.view.Surface.nativeUnlockCanvasAndPost(Native Method)
at android.view.Surface.unlockSwCanvasAndPost(Surface.java:382)
at android.view.Surface.unlockCanvasAndPost(Surface.java:363)
at android.view.ViewRootImpl.drawSoftware(ViewRootImpl.java:3451)
at android.view.ViewRootImpl.draw(ViewRootImpl.java:3339)
If i get the creation if the intent outside onChanged(), everything works fine. I have added a log statement and onChanged is not even triggered. So how can I move to the next activity without that error?
Edit:
public class SplashViewModel extends ViewModel {
private SplashRepository splashRepository;
MutableLiveData<String> idLiveData;
#Inject
SplashViewModel(SplashRepository splashRepository) {
this.splashRepository = splashRepository;
idLiveData = splashRepository.addIdToLiveData();
}
}
How I used live data in Splash Activity
class SplashActivity : BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override val mViewModel: SplashViewModel by currentScope.inject()
override fun getLayoutResId(): Int {
return R.layout.activity_splash
}
override fun initialization() {
}
override fun initializeObserver() {
mViewModel.liveData.observe(this, Observer {
if (it) {
launchActivityWithFinish<LoginActivity>()
}
})
}}
Here is My SplashViewModel
class SplashViewModel : BaseViewModel() {
val liveData = MutableLiveData<Boolean>()
init {
viewModelScope.launch {
delay(SPLASH_TIME_OUT)
liveData.postValue(true)
}
}}
My SplashActivity In java
public class SplashActivity1 extends BaseActivity<ActivitySplashBinding, SplashViewModel> {
#NotNull
#Override
protected SplashViewModel getMViewModel() {
return new SplashViewModel();
}
#Override
public int getLayoutResId() {
return R.layout.activity_splash;
}
#Override
public void initialization() {
}
#Override
public void initializeObserver() {
getMViewModel().getLiveData().observe(this, aBoolean -> {
if (aBoolean) {
Intent intent = new Intent(SplashActivity1.this, MainActivity.class);
intent.putExtra("id", "id");
startActivity(intent); //Go to MainActivity
finish();
}
});
}}
My SplashViewModel In Java
public class SplashViewModel extends BaseViewModel {
public MutableLiveData<Boolean> liveData = new MutableLiveData<>(false);
public SplashViewModel(){
new Handler().postDelayed(() -> liveData.postValue(true),SPLASH_TIME_OUT);
}}
It perfectly works for me in Kotlin & Java both
I had similar problems with navigation in the past. I usually fixed them by verifying in onChanged that the parameter (in your case the String id) is not null and inside call a view model method that changes the id to null.
viewModel.idLiveData.observe(this, new Observer<User>() {
#Override
public void onChanged(String id) {
if(id != null) {
viewModel.navigationDone();
Intent intent = new Intent(SplashActivity.this, MainActivity.class);
intent.putExtra("id", id);
startActivity(intent); //Go to MainActivity
}
}
});
where navigationDone is a view model method like:
void onNavigationDone() {
idLiveData.setValue(null);
}

AtomicReferences for Intents and Activity Navigation in Helper File

In my app, I navigate between about 5 different screens, each in its own activity. Pretty much any activity can be called from any other activity, so I am trying to build a helper file to manage the intents so that I don’t have redundant code.
I built a helper file with public static methods and pass the activity context and any required data when calling these methods. This appears to be working fine on my device (Samsung Galaxy S5), but Android Studio recommends making my intents AtomicReference in my helper file.
Can you help me understand if and why these should be AtomicReference<Intent>?
Also, is it appropriate to pass context to a helper file to make these calls?
ActivityHelper file:
public class ActivityHelper {
private ActivityHelper() {}
public static void startAddNewMealActivity(Context context) {
Intent newMealIntent = new Intent(context, MealEditActivity.class);
context.startActivity(newMealIntent);
}
public static void startMealListActivity(Context context) {
Intent intent = new Intent(context, MealListActivity.class);
context.startActivity(intent);
}
public static void startEditMealActivity(Context context, FBMeal meal, String mealFBKey) {
Intent intent = new Intent(context, MealEditActivity.class);
intent.putExtra(Constants.INTENT_FB_KEY_EXTRA_TAG, mealFBKey);
intent.putExtra(Constants.INTENT_MEAL_EXTRA_TAG, meal);
context.startActivity(intent);
}
public static void startEditLastMealActivity(final Context context) {
FBHelper.getQueryForMostRecentMeal().addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (FBHelper.isExistingDataSnapshop(dataSnapshot)) {
for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
FBMeal selectedMeal = snapshot.getValue(FBMeal.class);
String selectedMealId = snapshot.getKey();
startEditMealActivity(context, selectedMeal, selectedMealId);
}
} else {
Utils.showToastFromStringResource(R.string.no_meals, context);
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
Utils.showToastFromStringResource(R.string.error_getting_meal, context);
}
});
}
}
Example of calling helper file from menu in AppCompatActivity:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.edit_meal_menu:
ActivityHelper.startEditMealActivity(this, meal, mealFBKey);
return true;
case R.id.edit_last_entry_menu:
ActivityHelper.startEditLastMealActivity(this);
return true;
case R.id.about_menu:
DialogFragment newFragment = AboutDialog.newInstance();
newFragment.show(getFragmentManager(), "about");
default:
return super.onOptionsItemSelected(item);
}
}
I cannot see any reason at all why you would need to use an AtomicReference in any of your static methods.
Another approach would be to create a BaseActivity class that extends AppCompatActivity and includes all of your helper methods. All of your activities should then extend BaseActivity. In that case, you would not need to pass a Context to all of these helper methods, since the helper methods would be non-static and can just use this as Context.

Unit Test onSaveInstanceState with ActivityRules

I'm trying to figure out how to test onSavedInstance using the newer AndroidJunit4 and Activity Rules.
#RunWith(AndroidJUnit4.class)
public class MyViewActivityTest{
#Rule
public UiThreadTestRule uiThreadTestRule = new UiThreadTestRule();
#Rule
public ActivityTestRule<MyViewActivity> mActivityRule = new ActivityTestRule<>(MyViewActivity.class);
#UiThreadTest
#Test
public void testOnSavedIntanceState() {
uiThreadTestRule.runOnUiThread(new Runnable() {
#Override
public void run() {
Intent in = new Intent();
MyViewActivity activity = mActivityRule.launchActivity(in);
activity.finish();
activity.recreate();
}
});
}
I get an error not sure if I am barking up the right tree.
java.lang.IllegalStateException: Must be called from main thread
at android.app.Activity.recreate(Activity.java:4620)
You should be able to run the test with the annotation #UiThreadTest. It works for every test rule that extends UiThreadTestRule. In this case ActivityTestRule happens to do just that.
EDIT:
#UiThreadTest
#Test
public void testOnUIThread() {
// Test to run on UI thread
}
EDIT:
I just ran it again and remembered that you can't launch the activity on the UI thread. I made this and ran it without complications.
#RunWith(AndroidJUnit4.class)
public class TestActivity {
#Rule
public ActivityTestRule<MyViewActivity> activityRule = new ActivityTestRule<>(MyViewActivity.class, true, false);
#Test
public void testOnSavedInstanceState() throws Throwable {
activityRule.launchActivity(new Intent());
final Activity activity = activityRule.getActivity();
activityRule.runOnUiThread(new Runnable() {
#Override
public void run() {
activity.finish();
activity.recreate();
}
});
}

Activity crash from missing argument with Junit ActivityTestRule

I have an Espresso test suite for UI tests that looks like this:
#RunWith(AndroidJUnit4.class)
public class SpecialUiTests {
#Rule
public final ActivityTestRule<SpecialActivity> activity
= new ActivityTestRule<>(SpecialActivity.class);
#Test
public void specialTest() {
...
}
...
}
The problem is, that activity expects a bundle, and crashes when it can't find the value it expects
public class SpecialActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
final String specialValue = getIntent().getBundleExtra(ARG_SPECIAL_BUNDLE)
.getString(KEY_SPECIAL_VALUE);
//Do something with specialValue <--- Crash
}
...
}
Can I set up a test rule and still pass the parameter (a bundle) the activity expects?
#Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
SpecialActivity.class,
true, // initialTouchMode
false); //Lazy launching
#Test
public void specialTest() {
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putString(SpecialActivity.KEY_SPECIAL_VALUE, "789");
intent.putExtra(SpecialActivity.ARG_SPECIAL_BUNDLE, bundle);
activityRule.launchActivity(intent);
onView(withId(R.id.special))
.check(matches(withText("789")));
}
Source: http://blog.sqisland.com/2015/04/espresso-21-activitytestrule.html
You can also override getActivityIntent() of your ActivityTestRule to create the Intent. This way, an Activity with the appropriate Intent is started automatically for all of your test methods. Sample:
#Rule
public ActivityTestRule<SpecialActivity> mActivity = new ActivityTestRule<SpecialActivity>(SpecialActivity.class) {
#Override
protected Intent getActivityIntent() {
final Context targetContext = InstrumentationRegistry.getTargetContext();
final Intent intent = new Intent(targetContext, SpecialActivity.class);
intent.putExtra("arg_one", 1);
return intent;
}
};

Unit testing a broadcast receiver?

Here's a BroadcastReceiver from my project, which I'm looking to unit test. When the user makes a phone call, it grabs the phone number, and sets up an intent to start a new activity, passing in the phone number.
public class OutgoingCallReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context xiContext, Intent xiIntent)
{
if (xiIntent.getAction().equalsIgnoreCase(Intent.ACTION_NEW_OUTGOING_CALL))
{
String phoneNum = xiIntent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Intent intent = new Intent(xiContext, MyActivity.class);
intent.putExtra("phoneNum", phoneNum);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
xiContext.startActivity(intent);
setResultData(null);
}
}
}
So far, my unit test looks like this:
public class OutgoingCallReceiverTest extends AndroidTestCase
{
private OutgoingCallReceiver mReceiver;
#Override
protected void setUp() throws Exception
{
super.setUp();
mReceiver = new OutgoingCallReceiver();
}
public void testStartActivity()
{
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(getContext(), intent);
}
}
This runs through the code, but I want my test to be able to check that the intent was sent out, and to check the phone number on it. How do I do this?
Can I also test that the phone call gets cancelled (because of the setResultData(null) line)?
corlettk pointed me at the MockContext object in Android, which does the trick. I've made a subclass of it, TestContext, which looks like this:
public class TestContext extends MockContext
{
private List<Intent> mReceivedIntents = new ArrayList<Intent>();
#Override
public String getPackageName()
{
return "com.mypackage.test";
}
#Override
public void startActivity(Intent xiIntent)
{
mReceivedIntents.add(xiIntent);
}
public List<Intent> getReceivedIntents()
{
return mReceivedIntents;
}
}
And my test case now looks like this:
public class OutgoingCallReceiverTest extends AndroidTestCase
{
private OutgoingCallReceiver mReceiver;
private TestContext mContext;
#Override
protected void setUp() throws Exception
{
super.setUp();
mReceiver = new OutgoingCallReceiver();
mContext = new TestContext();
}
public void testStartActivity()
{
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(mContext, intent);
assertEquals(1, mContext.getReceivedIntents().size());
assertNull(mReceiver.getResultData());
Intent receivedIntent = mContext.getReceivedIntents().get(0);
assertNull(receivedIntent.getAction());
assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
}
}
Matt,
Sounds like you need to mock-up a Context ... and then swap your methods over to accepting interfaces instead of concrete classes: public void onReceive(IContext c, IIntent i), just for the purposes of testing. But then the Context and Intent classes aren't yours are they... they're Android's... so you can't "just" make them implement your interfaces, so you'd have to "wrap" them in order to expose a your interface, which is RATHER a lot of code for not much gain. Very Yucky!!!
So I started to wonder if someone's been through all this before, and done the hard-yards for us... and tada: http://developer.android.com/reference/android/test/mock/package-summary.html
Cheers. Keith.
Since this question was asked mocking Frameworks have evolved pretty much. With mockito you can now mock not only interfaces but as well classes. So I would suggest to solve this problem by mocking a context and using ArgumentCapture:
import static org.mockito.Mockito.*;
public class OutgoingCallReceiverTest extends AndroidTestCase {
private OutgoingCallReceiver mReceiver;
private Context mContext;
#Override
protected void setUp() throws Exception {
super.setUp();
//To make mockito work
System.setProperty("dexmaker.dexcache",
mContext.getCacheDir().toString());
mReceiver = new OutgoingCallReceiver();
mContext = mock(Context.class);
}
public void testStartActivity() {
Intent intent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
intent.putExtra(Intent.EXTRA_PHONE_NUMBER, "01234567890");
mReceiver.onReceive(mContext, intent);
assertNull(mReceiver.getResultData());
ArgumentCaptor<Intent> argument = ArgumentCaptor.forClass(Intent.class);
verify(mContext, times(1)).startActivity(argument.capture());
Intent receivedIntent = argument.getValue();
assertNull(receivedIntent.getAction());
assertEquals("01234567890", receivedIntent.getStringExtra("phoneNum"));
assertTrue((receivedIntent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0);
}
}

Categories

Resources