I want to get a reference to my activity before it is created in order to inject some mocks.
So I have my activity test rule like:
ActivityTestRule mActivityRule = new ActivityTestRule<MainActivity>(MainActivity,class, true, false)
I know that ActivityTestRule has a beforeActivityLaunched callback, but there I cant get any reference to my activity (is null there).
So if I do:
#Rule
public ActivityTestRule<MainActivity> mActivityTestRule = new ActivityTestRule(
MainActivity.class) {
#Override
protected void beforeActivityLaunched() {
super.beforeActivityLaunched();
MainActivity act = (MainActivity)getActivity();
//Here act is null
I cannot get a reference to my activity in the setUp method of my test class either. Activity reference is ready only when I do rule.launchActivity(), but then Activity is executed and I cannot pass any dependencies before. I know that I can use dagger for that, but I want to avoid it for this case. Is there any way to prepare the activity dependencies without dagger before it is launched?
If the second parameter (launchActivity) is false in ActivityTestRule it means you should customize the intent per test method
#Test
public void dummyTest() {
mActivityRule.launchActivity(new Intent());
// code here
}
Related
New to Android Unit Testing with Espresso, under #Rule, what is the purpose of creating a member variable? Does the name of the variable matter? I get the inkling that I need to tell the test unit which activity (or service, class) I'm testing, but is the variable and its scope used anywhere I need to care about?
#Rule
public ActivityTestRule<MenuActivity> mActivityTestRule = new ActivityTestRule<>(MenuActivity.class);
After doing some more practice and reserach with Android UI testing with Espresso, got many of the use cases for the #Rule variables. Of of which is the with testing Idling Resources (View and data that would happen async). Using the ActivityTestRule object (ex. mActivityTestRule) I can reference resources, fire public methods with tag #VisibleForTesting in that class.
ex.
// In the activity
#VisibleForTesting
#NonNull
public SimpleIdlingResource getmSimpleIdlingResource()
{
if (mSimpleIdlingResource == null)
{
mSimpleIdlingResource = new SimpleIdlingResource();
}
return mSimpleIdlingResource;
}
// In the Test class
// the test is run.
#Before
public void registerIdlingResource() {
mIdlingResource = mActivityTestRule.getActivity().getmSimpleIdlingResource();
}
When trying to create mock data in the #Before method of a JUnity4 test case, I'm not able to query the created data using Realm inside the Activity that was being tested.
The issue is that JUnity tests start the activity Before the #Before method runs.
This means that the data created on the test case wasn't available when the Activity started.
Solution:
Tell the test runner to not start the Activity before the tests run.
#Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class, false, false); // NOTE THE FALSES
Start the activity manually after creating the data you want.
#Before
public void before() {
// This must be the same config as the one being used by your app in the test.
final RealmConfiguration configuration = new RealmConfiguration.Builder(InstrumentationRegistry.getTargetContext())
.name(TaskerApplication.REALM_FILE)
.deleteRealmIfMigrationNeeded()
.schemaVersion(0)
.build();
realm = Realm.getInstance(configuration);
realm.beginTransaction();
createdObject = realm.copyToRealm(new AnyRealmObject());
realm.commitTransaction();
// Launch the Activity manually
activityRule.launchActivity(new Intent(Intent.ACTION_MAIN));
// Object will be available when queried from the Activity.
}
How to get the reference of Activity before its onCreate will be called. while its under test. I use ActivityTestRule as JUnit Rule. The reason for this requirement is i want to inject Mocks into activity from tests.
public class MyActivity extends Activity{
MyComponent myComponent;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
if(myComponent==null){
myComponent ... //initialise dagger component
}
myComponent.inject(this);
...
}
public void setComponent(MyComponent comp){
this.myComponent = comp;
}
}
public class MyTest{
#Rule
public ActivityTestRule<MyActivity> intentsTestRule = new ActivityTestRule<>(MyActivity.class);
MyComponent myFakeComponent;
#Before
public void setUp() {
MyActivity activity = intentsTestRule.getActivity();
activity.setComponent(myFakeComponent);
}
#Test
public void testMethod1(){...}
}
As per documentation, what you're doing here is wrong.
#Rule
public ActivityTestRule<MyActivity> intentsTestRule = new ActivityTestRule<>(MyActivity.class);
MyComponent myFakeComponent;
#Before
public void setUp() {
MyActivity activity = intentsTestRule.getActivity();
activity.setComponent(myFakeComponent);
}
Because,
This rule provides functional testing of a single activity.
The activity under test will be launched before each test annotated with
Test and before methods annotated with #Before.
It will be terminated after the test is completed and methods
annotated with After are finished. During the duration of the test
you will be able to manipulate your Activity directly.
However!
protected void beforeActivityLaunched ()
Override this method to execute any code that should run
before your Activity is created and launched.
This method is called before each test method,
including any method annotated with #Before.
Therefore, if you move the initialization of the MainActivityComponent outside the Activity to a place that is mockable, then you'll be able to tinker it together before the main activity is created.
EDIT:
Another possible solution is to lazily initiate the Activity as per link.
#Rule
public ActivityTestRule<NoteDetailActivity> mNoteDetailActivityTestRule =
new ActivityTestRule<>(NoteDetailActivity.class, true /* Initial touch mode */,
false /* Lazily launch activity */);
#Before
public void intentWithStubbedNoteId() {
// Add a note stub to the fake service api layer.
FakeNotesServiceApiImpl.addNotes(NOTE);
// Lazily start the Activity from the ActivityTestRule this time to inject the start Intent
Intent startIntent = new Intent();
startIntent.putExtra(NoteDetailActivity.EXTRA_NOTE_ID, NOTE.getId());
mNoteDetailActivityTestRule.launchActivity(startIntent);
registerIdlingResource();
}
Here is my sample code for that:
public class TestClass {
#Rule
public ActivityTestRule<T> activityRule = new ActivityTestRule<T>(type) {
#Override
protected void beforeActivityLaunched() {
//TODO inject mocks, setup stubs etc..
}
};
}
#Before
public void before() {
activityRule.getActivity();
}
#Test
public void myTest() {
//...
}
}
Is this code complete? I can't see you creating the dagger graph.
Anyway, what I do in my code, is to have a Static class called Injector that creates the graph for me, and also can inject elements into objects. So, in my Application Class I call it to create the graph, and all other activities just use the existent graph.
Then, in a test, you could create a fake test application class that initialize the graph in a different way, or simply recreate the graph calling the Injector methods, before the activity is created. I'm not familiar with ActivityTestRule, so I can't help much with the life cycle of this test.
But just make sure you create a new graph before the activity is created, and let the activity just use the existent graph.
How the activity access the graph? Well, I don't really love it, but we are used to access the application class (with explicit cast) and ask it to inject the dependencies for us. This is the way Dagger examples do it also.
I have a simple test today:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class WhenNavigatingToUsersView {
#Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity;
#Before
public void setActivity() {
mainActivity = mActivityRule.getActivity();
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
}
#Test
public void thenCorrectViewTitleShouldBeShown() {
onView(withText("This is the Users Activity.")).check(matches(isDisplayed()));
}
#Test
public void thenCorrectUserShouldBeShown() {
onView(withText("Donald Duck (1331)")).check(matches(isDisplayed()));
}
}
But for every test method the setActivity is run, which, if you have 10-15 methods, in the end will be time consuming (if you have a lot of views too).
#BeforeClass doesn't seem to work since it has to be static and thus forcing the ActivityTestRule to be static as well.
So is there any other way to do this? Rather than having multiple asserts in the same test method?
#Before annotation should only precede methods containing preliminary setup. Initialization of needed objects, getting the current session or the current activity, you get the idea.
It is replacing the old setUp() method from the ActivityInstrumentationTestCase2, just as #After replaces the tearDown().
That means that it is intended to be executed before every test in the class and it should stay that way.
You should have no ViewInteraction, no DataInteraction, no Assertions nor View actions in this method, since that is not its purpose.
In your case, simply remove the onView() call from setActivity() and put it inside the actual test methods, in every test method if necessary, like so:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class WhenNavigatingToUsersView {
#Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity;
#Before
public void setActivity() {
mainActivity = mActivityRule.getActivity();
// other required initializations / definitions
}
#Test
public void thenCorrectViewTitleShouldBeShown() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
onView(withText("This is the Users Activity.")).check(matches(isDisplayed()));
}
#Test
public void thenCorrectUserShouldBeShown() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
onView(withText("Donald Duck (1331)")).check(matches(isDisplayed()));
}
}
Another option for you would be separating these tests.
Clicking on the user's icon will be in the HomeActivity test class while the rest of the tests will be in the UserActivity test class.
UserActivity test class will launch UserActivity with the proper Intent ( you can do so by passing the false Boolean into the Rule constructor and calling launchActivity(intent) manually).
This will eliminate the necessity of setting up the activity every single time. It will also get rid of constant dependency on the main activity. If something goes wrong, your UserActivity tests will be intact and will produce the results, while the issue will be caught by the test in the MainActivity.
Actually, by doing so your tests will might become MediumSize as the runtime will drastically decrease.
You can try this :
**** Setting ****
public void testStory() throws Exception {
}
public void testStory2() throws Exception {
}
public void testStory3() throws Exception {
}
Try to run your test by this command:
./gradlew cC
Did you try doing it as follows or a minor variation of it to suit your needs:
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);
private MainActivity mainActivity = mActivityRule.getActivity();
#BeforeClass
public static void setActivity() {
onView(allOf(withId(R.id.icon), hasSibling(withText(R.string.users)))).perform(click());
}
This way, you 'mainActivity' need not be static. Also, the setActivity() method will get called only once.
the following is one of my Espresso test cases.
public void testLoginAttempt() {
Espresso.onView(ViewMatchers.withId(R.id.username)).perform(ViewActions.clearText()).perform(ViewActions.typeText("nonexistinguser#krossover.com"));
Espresso.onView(ViewMatchers.withId(R.id.username)).perform(ViewActions.clearText()).perform(ViewActions.typeText("invalidpassword"));
Espresso.onView(ViewMatchers.withId(R.id.login_button)).perform(ViewActions.click());
// AFTER CLICKING THE BUTTON, A NEW ACTIVITY WILL POP UP.
// Clicking launches a new activity that shows the text entered above. You don't need to do
// anything special to handle the activity transitions. Espresso takes care of waiting for the
// new activity to be resumed and its view hierarchy to be laid out.
Espresso.onView(ViewMatchers.withId(R.id.action_logout))
.check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
}
Currently what I did was to check if a view in the new activity (R.id.action_logout) is visibible or not. If visible, I will assume tha the activity opened successfully.
But it doesn't seem to work as I expected.
Is there a better way to check if a new activity is successfully launched instead of checking a view in that activity is visible?
Thanks
You can use:
intended(hasComponent(YourExpectedActivity.class.getName()));
Requires this gradle entry:
androidTestCompile ("com.android.support.test.espresso:espresso-intents:$espressoVersion")
The import for the intended() and hasComponent()
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
as mentioned by Shubam Gupta please remember to call Intents.init() before calling intended(). You can eventually call it in the #Before method.
Try:
intended(hasComponent(YourActivity.class.getName()));
Also, keep in mind
java.lang.NullPointerException is thrown if Intents.init() is not called before intended()
You may do it as follows:
#Test
public void testLoginAttempt() {
Espresso.onView(ViewMatchers.withId(R.id.username)).perform(ViewActions.clearText()).perform(ViewActions.typeText("nonexistinguser#example.com"));
Espresso.onView(ViewMatchers.withId(R.id.username)).perform(ViewActions.clearText()).perform(ViewActions.typeText("invalidpassword"));
Intents.init();
Espresso.onView(ViewMatchers.withId(R.id.login_button)).perform(ViewActions.click());
Intents.release();
}
java.lang.NullPointerException is thrown if Intents.init() is not called.
The problem is that your application performs the network operation after you click login button. Espresso doesn't handle (wait) network calls to finish by default. You have to implement your custom IdlingResource which will block the Espresso from proceeding with tests until IdlingResource returns back in Idle state, which means that network request is finished. Take a look at Espresso samples page - https://google.github.io/android-testing-support-library/samples/index.html
Make sure the Espresso intent library is in the gradle dependencies
androidTestImplementation "com.android.support.test.espresso:espresso-intents:3.0.1"
Then import these two in your test file
import static android.support.test.espresso.intent.Intents.intended
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent
Then add IntentsTestRule in your test class
#Rule
#JvmField
val mainActivityRule = IntentsTestRule(MainActivity::class.java)
Finally check the activity has launched intent
#Test
fun launchActivityTest() {
onView(ViewMatchers.withId(R.id.nav_wonderful_activity))
.perform(click())
intended(hasComponent(WonderfulActivity::class.java!!.getName()))
}
Try with
intended(hasComponent(new ComponentName(getTargetContext(), ExpectedActivity.class)));
Look at response from #riwnodennyk
I use this approach:
// The IntentsTestRule class initializes Espresso Intents before each test, terminates the host activity, and releases Espresso Intents after each test
#get:Rule
var tradersActivity: IntentsTestRule<TradersActivity> = IntentsTestRule(TradersActivity::class.java)
#get:Rule
var jsonViewActivity: IntentsTestRule<JsonViewActivity> = IntentsTestRule(JsonViewActivity::class.java)
#Test
fun scrollToItemAndClick() {
onView(withId(R.id.tradersRecyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(ITEM_POS, click()))
// check is activity was started
intended(hasComponent(JsonViewActivity::class.java.getName()))
}
#RunWith(RobolectricTestRunner.class)
public class WelcomeActivityTest {
#Test
public void clickingLogin_shouldStartLoginActivity() {
WelcomeActivity activity = Robolectric.setupActivity(WelcomeActivity.class);
activity.findViewById(R.id.login).performClick();
Intent expectedIntent = new Intent(activity, LoginActivity.class);
assertThat(shadowOf(activity).getNextStartedActivity()).isEqualTo(expectedIntent);
}
}