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();
}
});
}
Related
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;
}
};
when I try to replace dependencies using daggerMock, I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.test.espresso.intent.Intents.internalRelease()' on a null object reference
at android.support.test.espresso.intent.Intents.release(Intents.java:140)
at android.support.test.espresso.intent.rule.IntentsTestRule.afterActivityFinished(IntentsTestRule.java:68)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:260)
This is my Espresso Intents rule:
#Rule public IntentsTestRule<MyActivity> activityRule =
new IntentsTestRule<MyActivity>(MyActivity.class, true, false);
And this is my test
#Test
public void shouldDisplayToolbarOnStart() {
startActivity();
onView(withId(R.id.toolbar)).check(matches(isDisplayed()));
}
private MyActivity startActivity() {
return activityRule.launchActivity(null);
}
My Espresso custom rule:
public class EspressoDaggerMockRule extends DaggerMockRule<RootComponent> {
public EspressoDaggerMockRule() {
super(RootComponent.class, new MainModule(getApp()));
set(new DaggerMockRule.ComponentSetter<RootComponent>() {
#Override public void setComponent(RootComponent component) {
getApp().setComponent(component);
}
});
}
private static App getApp() {
return (App) InstrumentationRegistry.getInstrumentation().getTargetContext().getApplicationContext();
}
}
Temporary workaround
Found a way to stop crashing:
#Rule public IntentsTestRule<MyActivity> activityRule =
new IntentsTestRule<MyActivity>(MyActivity.class, true, false);
replaced by
#Rule public ActivityTestRule<MyActivity> activityRule =
new ActivityTestRule<MyActivity>(MyActivity.class, true, false);
I have an Android fragment that I want to test. I created a test activity to which I add this fragment and run some Espresso tests.
However, Espresso does not find any of the views inside the fragment. It dumps the view hierarchy and it is all empty.
I do not want to use the actual parent activity. I want to just test this fragment in isolation.
Has anyone done this? Is there a sample that has a similar code?
#RunWith(AndroidJUnit4.class)
class MyFragmentTest {
#Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
TestActivity.class);
#Test
public void testView() {
MyFragment myFragment = startMyFragment();
myFragment.onEvent(new MyEvent());
// MyFragment has a recyclerview.
//OnEvent is EventBus callback that in this test contains no data.
//I want the fragment to display empty list text and hide the recyclerView
onView(withId(R.id.my_empty_text)).check(matches(isDisplayed()));
onView(withId(R.id.my_recycler)).check(doesNotExist()));
}
private MyFragment startMyFragment() {
FragmentActivity activity = (FragmentActivity) activityRule.getActivity();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
MyFragment myFragment = new MyFragment();
transaction.add(myFragment, "myfrag");
transaction.commit();
return myFragment;
}
}
I will do in following way
Create a ViewAction as follows:
public static ViewAction doTaskInUIThread(final Runnable r) {
return new ViewAction() {
#Override
public Matcher<View> getConstraints() {
return isRoot();
}
#Override
public String getDescription() {
return null;
}
#Override
public void perform(UiController uiController, View view) {
r.run();
}
};
}
Then use below to launch code which should be run in UI Thread
onView(isRoot()).perform(doTaskInUIThread(new Runnable() {
#Override
public void run() {
//Code to add your fragment or anytask that you want to do from UI Thread
}
}));
below is an example of test case adding fragment view hierarchy
#Test
public void testSelectionOfTagsAndOpenOtherPage() throws Exception{
Runnable r = new Runnable() {
#Override
public void run() {
//Task that need to be done in UI Thread (below I am adding a fragment)
}
};
onView(isRoot()).perform(doTaskInUIThread(r));
}
public class VoiceFullScreenTest {
#Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(
TestActivity.class);
#Test
public void fragment_can_be_instantiated() {
activityRule.getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
VoiceFragment voiceFragment = startVoiceFragment();
}
});
// Then use Espresso to test the Fragment
onView(withId(R.id.iv_record_image)).check(matches(isDisplayed()));
}
private VoiceFragment startVoiceFragment() {
TestActivity activity = (TestActivity) activityRule.getActivity();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
VoiceFragment voiceFragment = new VoiceFragment();
transaction.add(voiceFragment, "voiceFragment");
transaction.commit();
return voiceFragment;
}
}
You can start your fragment from UI thread as mentioned above.
You can use FragmentTestRule.
Instead of the regular ActivityTestRule you must use:
#Rule
public FragmentTestRule<?, FragmentWithoutActivityDependency> fragmentTestRule =
FragmentTestRule.create(FragmentWithoutActivityDependency.class);
You can find more details in this blog post.
You can use the androidx.fragment:fragment-testing library. Launching the fragment in your test method is as simple as:
val fragmentArgs = Bundle()
androidx.fragment.app.testing.launchFragmentInContainer<MyFragment>(fragmentArgs)
You can find more information about this library in the Test your fragments Android Developers' guide.
You've probably forgot to inject the fragment in the view hierarchy. Try defining the holder container for your fragment in the TestActivity layout (like a FrameLayout with id fragment_container) and then instead of just add(myFragment, "tag"), use the add(R.id.fragment_container, myFragment, "tag") (this method). I guess you could use the replace method with the same signature as well.
I'm developing application based on MVP pattern using retrofit to perform networking. I want to unit test my presenter but it fails.
In my app dataView implements DataView which is mocked by Mockito. In
DataPresenter in onViewCreated method MyApi instance is get from MyApplication and it performs request. Anonymous Subscriber<Data> onNext calls showData(Data data) on dataView. Unfortunatelly Mockito.verify(dataView).showData(data) fails the test. I mocked retrofit client by my self to response in deterministic way.
Code below:
public class DataFragment extends ProgressFragment implements DataView {
protected DataPresenter mDataPresenter;
//[...] initialization arguments boilerplate etc.
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mDataPresenter.onViewCreated(mId);
//[...]
}
#Override
public void startLoading() {
setContentShown(false);
}
#Override
public void stopLoading() {
setContentShown(true);
}
#Override
public void showData(Data data) {
setContentEmpty(false);
//[...] present data
}
#Override
public void showError() {
setContentEmpty(true);
setEmptyText(R.string.unknown_error);
}
}
In DataPresenter:
#Override
public void onViewCreated(long id) {
getView().startLoading();
MyApplication.getInstance().getMyApi().checkIn(User.getUser().getFormattedTokenForRequest(),
(int) id).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(new Subscriber<Data>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
getView().showError();
getView().stopLoading();
}
#Override
public void onNext(Data data) {
getView().showData(data);
getView().stopLoading();
}
});
;
}
My test case:
public static final String GOOD_RESPONSE = "[Data in JSON]"
public static final int GOOD_STATUS = 201;
#Mock
DataView mDataView;
#Mock
MyApplication app;
#Mock
SharedPreferencesManager mSharedPreferencesManager;
DataPresenter mDataPresenter;
#Before
public void setUp() throws Exception {
mDataPresenter = new DataPresenterImpl(mDataView);
MyApplication.setInstance(app);
Mockito.when(app.getSharedPreferencesManager()).thenReturn(mSharedPreferencesManager);
Mockito.when(mSharedPreferencesManager.getUser()).thenReturn(null);
}
#Test
public void testCase() throws Exception {
RestAdapter adapter = (new RestAdapter.Builder()).setEndpoint(URL)
.setClient(new MockClient(GOOD_RESPONSE, GOOD_STATUS))
.build();
Mockito.when(app.getMyApi()).thenReturn(adapter.create(MyApi.class));
mCheckInPresenter.onViewCreated(3);
Mockito.verify(checkInView).startLoading();
Mockito.verify(checkInView).showData(new Data());
}
Test fails on "Wanted but not invoked:
dataView.showData(..." .
What is interesting Response execute() is called in MockClient but onNext(Data data) in subscriber included in DataPresenterImpl is not. Any ideas? I guess it is a problem with request being asynchronous.
The problem is that the work is being sent to a different thread and mockito cant verify whats going on. My solution to this would be to create a scheduler factory and mock it out and return the main thread for tests
like these. Something like:
public class schedulerFactory {
public Scheduler io() {
return Schedulers.io();
}
//etc
}
then in your test you would write something like this:
#Mock SchedulerFactory factory
#Before
public void setUp() {
when(factory.io()).thenReturn(Schedulers.mainThread());
}
in general its a good idea to run all the code in the same thread for testing
My app has only one activity and based on many fragments. How I can test this fragment in a right way? Give me an example, please.
Cause I try this test class:
#LargeTest
public class ActivityTest extends ActivityInstrumentationTestCase2<ActivityEx> {
public ActivityTest() {
super(ActivityEx.class);
}
public void setUp() throws Exception {
super.setUp();
getActivity();
}
public void testTest() {
//simple example
assertEquals(true, true);
}
}
And in result I've failed due to ClassCastException.
Rather do it like this:
public class ActivityTest extends android.test.ActivityInstrumentationTestCase2
{
public ActivityTest()
{
super(ActivityEx.class);
}
#Override
protected void setUp() throws Exception
{
super.setUp();
getActivity();
}
public void testTest() {
//simple example
assertEquals(true, true);
}
}
If you interested I also posted a tutorial on testing fragments http://www.stevenmarkford.com/testing-fragments-with-android-espresso-basic-example/