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()
Related
I have a helper class to generate screenshots when using Espresso. Screenshots are generated by using Firebase ScreenShotter. My code is as follows:
private static ActivityScenario activityScenario;
public static void setup(){
activityScenario = ActivityScenario.launch(MainActivity.class);
}
public static void screenshot(String name){
activityScenario.onActivity(activity->{
ScreenShotter.takeScreenshot(name, activity /* activity */);
});
}
So another class would call it like this:
#Before
public void setup(){
SetupHelper.setup();
}
#Test
public void loginAfterReset() {
SetupHelper.screenshot("Home");
}
The screenshots do save to the SD card of the device. However, the name of the screenshot on the SD card is:
UnknownTestClass-unknownTestMethod-Home-1.jpg
Why does it come up as UnknowntestClass-unknownTestMethod? How do I have more control over the naming?
ScreenShotter uses the stack trace to figure out the filename, based on the test class and test method that it can find in the stack trace.
ActivityScenario.onActivity() runs the passed action on the current Activity's main thread. Tests are executed on a different thread. So, the action that you pass will have a Stack trace that doesn't include the test method or test class in it. That's the reason why you see UnknownTestClass-unknownTestMethod. When the anonymous function is executed, it's not running "in the context" of the test class.
One way to fix this is not using onActivity(). ActivityScenario.launch() should already bring your Activity into the "resumed" state, i.e. make it visible. Change your SetupHelper.screenshot() to the following:
public static void screenshot(String name){
ScreenShotter.takeScreenshot(name, activity /* activity */);
}
This will change the screenshot filename to include the actual test class and test method name.
I have an activity in which I provide a button. Clicking on the button invokes a method in a data provider class and based on the return value of the method I make UI changes. Now I want to write an instrumented test where I perform click() in the button but avoid actually calling the method in the data provider class. Instead I want to return a desired value from the method and then check if the UI was modified accordingly.
MyActivity
#Override
public void onCreate(final Bundle savedInstanceState) {
mActionButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(final View v) {
boolean result = dataProvider.getResult();
if(result) {
mSuccessTextView.setVisibility(View.VISIBLE);
}
}
});
}
Here, on button click, a call is made to DataProvider#getResult and the result from this method is stored in result. If the result is true a TextView mSuccessTextView, previously GONE, is now made VISIBLE.
The problem here is DataProvider#getResult deals with a lot of external components that would make testing impossible. So what I want to do is use a mocked instance of DataProvider so that I can get getResult to return a desired value and then check the visibility of mSuccessTextView. This is what I tried :
MyActivityTest.java
#RunWith(AndroidJUnit4.class)
public class MyActivityTest {
private DataProvider mDataProvider;
#Rule
public IntentsTestRule<MyActivity> mIntentRule =
new IntentsTestRule<>(MyClientActivity.class);
#Before
public void setUp() {
mDataProvider = mock(DataProvider.class);
}
#Test
public void testResultSuccess() {
boolean result = true;
when(mDataProvider.getResult()).thenReturn(result);
onView(withId(R.id.action_button)).perform(click());
onView(withId(R.id.success_text_view)).check((ViewAssertion) isDisplayed());
}
}
Doing the above generates the following error :
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.domain.myapp.DataProvider.
Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.
Underlying exception : java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.IllegalStateException: This JVM's version string does not seem to be valid: 0
.
.
.
Even if you could mock DataProvider, it would not help you because you are not injecting its instance into MyClientActivity during your test. Reasons for not able to mock DataProvider are unknown, pls provide the class.
I need to write a UI test to validate that clicking the floating action button results in displaying the SecondActivity.
public class MainActivityTest {
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class);
#Before
public void setUp() throws Exception {
}
#Test
public void onClick() throws Exception {
onView(withId(R.id.button)).perform(click());
}
}
Is it enough?
And how can I validate that the Activity properly displays the text contents of an incoming object: name, age, phone number?
I have just started using espresso(
Is it enough?
No, it is not enough. This code
onView(withId(R.id.button)).perform(click()); only performs a click on the button, but there is nothing that verifies that the application behaved correctly after that.
To verify that an intent to open the SecondActivity was created, you need to use Espresso Intents.
how can I validate that the Activity properly displays the text contents of an incoming object
You can use something like:
onView(withId(R.id.textView)).check(matches(withText("Expected text")));
Take a look at the Espresso Cheatsheet for more.
I have MainActivity that shows FragmentDialog (EditIntervalFragment) in order to capture user's input. Activity implements EditIntervalListener interface. In onAtach method fragment casts activity to EditIntervalListener.
I want to test that my EditIntervalFragment properly calls EditIntervalListener methods with correct parameters.
My initial intent was to use Roblectric and Mockito. The following code almost works.
#Test
public void shouldCallInterfaceAfterModify() {
MainActivity hostActivity = Robolectric.setupActivity(MainActivity.class);
EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
editIntervalFragment.show(hostActivity.getSupportFragmentManager(), "test");
AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
assertNotNull(dialog);
EditIntervalFragment.EditIntervalListener activity = Mockito.spy(hostActivity);
dialog.findViewById(android.R.id.button1).performClick();
verify(activity).onIntervalChanged(0,TEST_NAME,TEST_DURATION);
}
The problem with this code that it uses real MainActivity. It means that all MainActivity's logic will be executed. I want to avoid this. How can I do this?
Update
I found a way to not call real MainActivity. I created another activity, just for test.
public class ActivityTest extends FragmentActivity implements EditIntervalFragment.EditIntervalListener {
//empty methods here
}
My test now looks like this
#Test
public void shouldCallInterfaceAfterModify() {
ActivityTest hostActivity = Robolectric.setupActivity(ActivityTest.class);
ActivityTest spy = Mockito.spy(hostActivity);
EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
editIntervalFragment.show(spy.getSupportFragmentManager(), "test");
AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
assertNotNull(dialog);
dialog.findViewById(android.R.id.button1).performClick();
verify(spy).onIntervalChanged(0, TEST_NAME, TEST_DURATION);
}
But after test execution I receive error saying than only spy.getSupportFragmentManager() was called. I'm 100% sure that onIntervalChanged should be called.
Looking for help. How can I implement such kind of test?
That is always challange to make work spies when you don't control lifecycle.
What we are usually doing we extracting all not related functionality to utility classes and mock them in tests. It also helps with design of the application (Single class responsibility rule).
Of course it depends if you do something with this data. If it is just data class than I would have Factory for creating this data classes and again mock it in tests. All this requires proper DI (look to Dagger).
And there is nothing wrong with your approach but it doesn't force you to think about your app as small parts that interact with each other. But at the same time it brings more complexity which pays off later
I ended up with this solution. Create an Activity that implements interface an keep track of all interaction.
public class ActivityTest extends FragmentActivity implements EditIntervalFragment.EditIntervalListener {
public int mIntervalChangedCalls = 0;
public int mPosition;
public String mName;
public long mDurationMillSec;
#Override
public void onIntervalChanged(int position, String name, long durationMillSec) {
mIntervalChangedCalls++;
mPosition = position;
mName = name;
mDurationMillSec = durationMillSec;
}
}
My test looks like this
#Test
public void shouldCallOnIntervalChanged() {
ActivityTest hostActivity = Robolectric.setupActivity(ActivityTest.class);
EditIntervalFragment editIntervalFragment = EditIntervalFragment.getInstance(0, TEST_NAME, TEST_DURATION);
editIntervalFragment.show(hostActivity.getSupportFragmentManager(), "test");
AlertDialog dialog = (AlertDialog) editIntervalFragment.getDialog();
assertNotNull(dialog);
dialog.findViewById(android.R.id.button1).performClick();
assertThat(hostActivity.mIntervalChangedCalls).isEqualTo(1);
assertThat(hostActivity.mPosition).isEqualTo(0);
assertThat(hostActivity.mName).isEqualTo(TEST_NAME);
assertThat(hostActivity.mDurationMillSec).isEqualTo(TEST_DURATION);
}
I'm not completely happy with this creation of a separate class just for test purposes. I suppose the same can be achieved with Mockito or Robolectric, but I do not know how.
So I'm still open for any ideas or suggestions. I'll accept my own answer, if no one gives better solution in a week.
I'm using Robolectric to perform unit test on an android app.
The problem is simple : i can get my Button using findViewById, but calling on it either performClick()method or Robolectric.clickOn() will return false.
However the button works perfectly, tested through the app or with Robotium unit tests...
here is the test code that doesn't pass :
#RunWith(RobolectricTestRunner.class)
public class RegisterActivityTest {
private MainActivity mainActivity;
private LoginActivity loginActivity;
#Before
public void setUp(){
mainActivity=new MainActivity();
mainActivity.onCreate(null);
mainActivity.setContentView(R.layout.main);
}
#Test
public void testButton() throws Exception {
Button buttonPayment=(Button)mainActivity.findViewById(R.id.btn_payment);
assertNotNull(buttonPayment); // will pass test ( ---> the view is a button and exists )
assertTrue(buttonPayment.performClick()); // raise AssertionError
assertTrue(Robolectric.clickOn(buttonPayment)); // raise AssertionError as well
}
}
Thanks everybody.
Paul
According to the documentation for performClick(), the method will return true if you assigned an OnClickListener to the button.
...Did you?
Typically you would call setContentView within your onCreate method. Thus, the call to setContentView in your setUp method is redundant and may be causing issues.