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.
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.
DB : SQLite
Table : Contact
Espresso test:
#Test
public void testBlock() {
onData(anything()).inAdapterView(withId(R.id.container_ListView)).atPosition(0).onChildView(withText(R.string.block_user))
.perform(click());
}
And test success pass. But it succeeds only if before starting the contact has the status unblock (column Status in db table). So I need (before starting the test) to update this Contact to status unblock. How can I do this? Or has anyone a better solution for this?
This is something you can either do it in a #Before method or within your testcase before starting the activity.
First you need to change your activity rule to this:
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class, false, false);
Note the last false flag, this says that your activities are not launched automatically when your test start.
Then in your test class you could add this method:
#Before
public static void PrepareDatabase() {
// Instantiate your Service that performs an action on the database
// give it this context: InstrumentationRegistry.getTargetContext();
// For example the code could look like this:
DatabaseHelper(InstrumentationRegistry.getTargetContext()).setYourValue();
mActivityRule.launchActivity(null); //launches the test activity
}
Then normally write your test. This code performs some operation on your database and afterwards launches your activity for the test. You can also pass this into your test method instead of in the #Before method.
I want to know how to set an item in a spinner in espresso testing.
onView(withId(R.id.spinner_gender)).perform(click());
onData(allOf(is(instanceOf(String.class)))).atPosition(0).perform(click());
This code above does not work :/
Your code snippet looks correct, so there may be an issue with another part of your test class?
Are you getting an Exception or stack-trace you can update your question with? Also check the espresso documentation for a bit more explaination.
See small code example of how you can select a spinner option by text or it's position.
#RunWith(AndroidJUnit4.class)
public class BasicEspressoTest {
#Rule
public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void selectBySpinnerPosition() throws Exception {
onView(withId(R.id.spinner)).perform(click());
onData(allOf(is(instanceOf(String.class)))).atPosition(0).perform(click());
}
#Test
public void selectBySpinnerText() throws Exception {
onView(withId(R.id.spinner)).perform(click());
onData(allOf(is(instanceOf(String.class)), is("spinner's text"))).perform(click());
}
}
I am working on pushing a project into espresso testing currently. I have read a bunch of documents and follow the given practises to get started.
Everything works fine, However, when it comes to Intents related test, the result is strange.
Most of the time, the tests passed in my Mac but fail in my colleague's Windows(not all tests fail) with the the fail message java.lang.IllegalStateException: init() must be called prior to using this method.
Quite strangely, If we Run Debug test in Android Studio flow the code step by step, it passes.
here is the test code:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class MainActivityTest {
#Rule public IntentsTestRule<MainActivity> mRule = new IntentsTestRule<>(MainActivity.class, true, false);
AccountManager accountManager;
MainActivity activity;
private void buildLoginStatus() throws AuthenticatorException {
DanteApp app = (DanteApp) InstrumentationRegistry.getTargetContext().getApplicationContext();
accountManager = app.getDanteAppComponent().accountManager();
DoctorModel doctorModel = AccountMocker.mockDoctorModel();
accountManager.save(doctorModel.doctor);
accountManager.setAccessToken(doctorModel.access_token, false);
}
#Before public void before() throws Exception {
buildLoginStatus();
// must login
assertThat(accountManager.hasAuthenticated(), is(true));
activity = mRule.launchActivity(null);
// block all of the outer intents
intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
}
#After public void tearDown() throws Exception {
accountManager.delete();
}
// failed
#Test public void testViewDisplay() throws Exception {
// check tabhost is displayed
onView(withClassName(equalTo(TabHost.class.getName()))).check(matches(isDisplayed()));
// check toolbar is displayed
onView(withClassName(equalTo(ToolBar.class.getName()))).check(matches(isDisplayed()));
}
// passed
#Test public void testCallServiceHotline() throws Exception {
// switch to the account tab layout
onView(withChild(withText(R.string.account))).perform(click());
// click account menu to make a service call
onView(withId(R.id.contact)).perform(click());
// check call start expectly
intended(allOf(
not(isInternal()),
hasAction(Intent.ACTION_DIAL),
hasData(Uri.parse("tel:" + activity.getString(R.string.call_service)))
));
}
// failed
#Test public void testOpenSettingsUI() throws Exception {
// stub all internal intents
Intents.intending(isInternal())
.respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
onView(withChild(withText(R.string.account))).perform(click());
onView(withId(R.id.setting)).perform(click());
// check open settings activity successfully
intended(anyOf(
hasComponent(SettingActivity.class.getName())
));
}
}
The testing library version(nearly all dependencies are up to date and we use both physics devices and emulator to test):
rule: 0.4.1
runner: 0.4.1
espresso-*: 2.2.1
support-*: 23.1.0
Any idea deserves an appreciation. Thanks!
Two Solutions:
Use ActivityTestRule instead of IntentsTestRule and then in your #Before and #After manually call Intents.init() and Intents.release() respectively.
Write a custom IntentTestRule and override beforeActivityLaunched() to include your AccountManager logic. Use afterActivityFinished for your current #After logic. This will also allow you to just use the default IntentTestRule constructor. (Preferred Solution)
As to why this is happening:
"Finally on an unrelated note, be careful when using the new IntentsTestRule. It does not initialize, Intents.init(), until after the activity is launched (afterActivityLaunched())." - Shameless plug to my own post (halfway down helpful visual)
I think you are running into a race condition where in your #Before method you are executing launchActivity() then espresso tries to execute intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null)); before your activity is actually created, which means afterActivityLaunched() isn't called, which means neither is Intents.init(), crash!
Hope this helps.
IntentsTestRule is derived from ActivityTestRule and should manage Intents.init() and Intents.release() for you.
However, in my case the IntentsTestRule did not work properly. So I switch back to ActivityTestRule and call Intents.init() before and Intents.release() after the test which sent the Intent.
For more information please see this reference.
I'm trying to write a unit test for my android app but having trouble doing what I want with mockito. This is being used in conjunction with Robolectric which I have working just fine and have demonstrated that unit tests work.
I want to test whether or not a button will open a new activity depending on whether there is some bluetooth device connected. Obviously, there is no device connected with bluetooth in my test, however I want to pretend as though there is. The state of the bluetooth connection is stored in my Application class. There is no publicly accessible method to change this value.
So basically the logic in the app is like this:
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication();
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
So to test this I did the following:
TestHomeActivity.java:
#Test
public void buttonShouldOpenListIfConnected() {
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
//listViewButton was setup in #Before
listViewButton.performClick();
ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
Intent intent = shadowActivity.getNextStartedActivity();
assertNotNull(intent); //this fails because no new activity was opened. I debugged this and found that isDeviceConnected returned false.
ShadowIntent shadowIntent = Robolectric.shadowOf(intent);
assertThat(shadowIntent.getComponent().getClassName(), equalTo(ListActivity.class.getName()));
}
So my unit test fails because the call (in the activity) to isDeviceConnected returns false even though I thought I told it to return true with the mock framework. I want my test to have this method return true though. Isn't this what mockito does or am I totally mistaken on how to use mockito?
That's how mockito works, but the problem is: is your listViewButton using your mockedApp? Seems not, because you're creating mockedApp at the test method and never setting it anywhere. Mockito will not mock the method calls of all instances of Application, only from what you declared as a mock.
I personally don't know how android works with the Application class, but you will have to set it somewhere so listView use your mockedApp instead of what it receives normally.
EDIT
After the updated question, you can transform your getApplication in a protected method, spy you listViewButton and make it return your mockedApp. That smells a little bad, but it's one way if you can not set your application mocked object to listViewButton.
EDIT2
Example of using spy in your test using BDDMockito for readability :)
public HomeActivity {
...
protected MyApplication getApplication() {
// real code
}
...
}
public void TestHomeActivity {
private HomeActivity homeActivity;
#Before
public void setUp() {
this.homeActivity = spy(new HomeActivity());
}
#Test
public void buttonShouldOpenListIfConnected() {
// given
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
// IMPORTANT PART
given(homeActivity.getApplication()).willReturn(mockedApp);
...
}
}
After that, your test should work as expected. But I reinforce: Use spy only if you can't inject your mockedApp inside HomeActivity.
Your mocked version isn't being called.
See that call, getApplication()? (below). That's returning a real copy of your MyApplication class, not your mocked version. You'd need to intercept the getApplication() call and pass in your mocked Application object.
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication(); // returns the real thing
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
I'm not sure this is possible with Mockito. Have you tried customizing the ShadowActivity#getApplication() method?