Is it possible to test an Abstract activity with Robolectric - android

I use abstract activity classes in my code to well, abstract away some features from the activity classes.
I'm trying to test the abstract activity classes using Robolectric and the gradle-android-test-plugin using subclasses that extend the abstract class. I can't seem to get it to work though.
Does anyone have any experience in this area and is it even possible ? Basic structure is :
#RunWith(RobolectricGradleTestRunner.class)
public class AbstractActivityTest {
private ActivityTest activity;
#Before
public void setUp() throws Exception {
activity = Robolectric.buildActivity(ActivityTest.class).create().get();
}
private class ActivityTest extends AbstractActivity {
// do something
}
}
Initially, I got the error message the sub class wasn't static so I made it static. Now I get the following two fails:
initializationError FAILED
java.lang.Exception: Test class should have exactly one public constructor
initializationError FAILED
java.lang.Exception: No runnable methods
Any obviously true tests I put in #Test methods succeed.

The first error saying that you added non-default constructor to your test class or changed access level for default one. But as it says junit Test class should have at least one public constructor.
The second one says that at least one method in test class should have #Test annotation (junit 4) or starts with test substring (junit 3).

Yo can doing exactly what you are trying to do: subclass the abstract activity and instance the concrete class.
However, you need to declare the class extending the abstract Activity in it's own public file. If it's a nested class Robolectric will fail to instance it.
I don't know why, though.

I test an abstract activity this way:
1. Creating the abstract avtivity:
public abstract class AbstractActivity extends AppCompatActivity {
public int getNumber() {
return 2;
}
}
2. Creating the test class:
You just need to declare a static nested subclass of your abstract class.
#RunWith(RobolectricTestRunner.class)
public class AbstractActivityTest {
#Test
public void checkNumberReturn() throws Exception {
TestAbstractActivity testAbstractActivity = Robolectric.setupActivity(TestAbstractActivity.class);
assertThat(testAbstractActivity.getNumber(), is(2));
}
public static class TestAbstractActivity extends AbstractActivity {
}
}

Related

How do I provide a custom Application class to my Espresso Activity test?

I'm pretty new to Espresso, but I am trying to test a relatively simple Activity. My android app has its own custom Application class. How can I tell Espresso to use a mocked (or custom) version of this class?
Here is my custom version of the Application. It creates some test data (edited here for brevity). Down the road, I will also be overriding some of the methods.
public class MockMyApplication extends MyApplication {
#Override
public void onCreate() {
super.onCreate();
// create some location data for testing
DataRecord rec = new DataRecord(1);
rec.setName("TestLoc1");
rec.setDescription("an important customer");
MyData.add(rec);
}
}
My attempt to test using this, looks like this:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class LocEditActivityTest extends AndroidJUnitRunner {
#Rule
public ActivityTestRule<LocEditActivity> activityTestRule
= new ActivityTestRule<>(LocEditActivity.class);
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
#Test
public void testActivity_ExistingLoc() {
Intent i = new Intent();
i.putExtra("loc",1);
activityTestRule.launchActivity(i);
onView(withId(R.id.editName)).check(matches(withText("TestLoc1")));
// shutdown
onView(withContentDescription("Navigate up")).perform(click());
}
}
Using a debugger, I have determined that when LocEditAcitivity's onCreate calls getApplication(), it returns a MyApplication class with empty data, and not the MockedMyApplication with my test data.
Found it!
Looks like I misunderstood the "Runner" class usage. I needed to create my own Runner that extended AndroidJUnitRunner:
import android.app.Application;
import android.content.Context;
import android.support.test.runner.AndroidJUnitRunner;
// Our own test runniner - uses MockMyApplication as a mocked app class
public class MyAndroidTestRunner extends AndroidJUnitRunner {
#Override
public Application newApplication(ClassLoader cl, String className, Context context) throws IllegalAccessException, ClassNotFoundException, InstantiationException {
return super.newApplication(cl, MockMyApplication.class.getName(), context);
}
}
And then in build.gradle (app), the testInstrumentationRunner entry needs to point to the new runner:
testInstrumentationRunner "com.winwaed.xyzapp.MyAndroidTestRunner"
As the newApplication override was in the wrong place, this should be removed from my test class. Also, the test class no longer extends any classes. (ie. I essentially split the runner and test classes - as I said, I misunderstood the runner class)

How to call onCreate of application class while running activity tests using Espresso

In my Android app, I have an Application class which extends MultiDexApplication. Let's call it MyApplicationClass In the onCreate() of MyApplicationClass.java, I set some static variables. Then in the onCreate() method of all the activities I get those variables using static methods.
public class MyApplicationClass extends MultiDexApplication {
private static String value;
public static void setValue(String value) {
MyApplicationClass.value = value;
}
public static String getValue() {
return MyApplicationClass.value;
}
}
Now using Espresso framework, I am writing UI tests for an activity using the following code
public class MyActivityTest{
#Rule
public ActivityTestRule activityTestRule =
new ActivityTestRule(MyActivity.class);
#Test
public void testButtonIsVisible() {
//Some Test code.
}
}
After running the test from Android Studio, onCreate() method of MyActivity gets called and its tries to get the static variables. But the value of those variables is null. The reason is fairly simple. onCreate() of MyApplicationClass.java doesn't get called in the process.
So how do I call the onCreate() method of an Application class before launching the activity in Espresso?
P.S. Please don't advice regarding the setting and getting of static variables. That the requirement of the code.
I had the same problem and spent a lot of time until I realized that we use a custom TestRunner and ovveride the newApplication method. Because of this during tests we used another class for the Application with an empry onCreate method.
Once I switched to the default TestRunner the onCreate from the application was executed as expected.
#Override
#NonNull
public Application newApplication(#NonNull ClassLoader cl,
#NonNull String className,
#NonNull Context context)
throws InstantiationException,
IllegalAccessException,
ClassNotFoundException {
return super.newApplication(cl,TestPLayerApplication.class.getName(), context);
}

Is there a way to run Espresso test with multiple test methods but only one setup method?

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.

Adding Test Module for RoboGuice When Using Robolectric 2

I am currently upgrading robolectric from version 1 to 2. In my current version I use the following to provide the test module (for binding) to roboguice.
public class RoboTestRunner extends RobolectricTestRunner {
public RoboTestRunner(Class<?> testClass) throws
InitializationError {
super(testClass);
}
#Override
public void prepareTest(Object test) {
Application app = Robolectric.application;
RoboGuice.setBaseApplicationInjector(app, RoboGuice.DEFAULT_STAGE,
Modules.override(RoboGuice.newDefaultRoboModule(app)).with(new
TestModule()));
Injector injector = RoboGuice.getInjector(app);
injector.injectMembers(test);
}
}
However now I have upgraded the prepareTest method is not in this class. Where should I run this code in the new version?
UPDATE
I have found the way to do this. I need to create an class which extends android.app.Application in the project and reference this in the Manifest. Then I create a class like so
public class TestApplication extends Application implements TestLifecycleApplication {
#Override
public void onCreate() {
super.onCreate();
RoboGuice.setBaseApplicationInjector(this, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(this), new TestModule());
}
#Override
public void beforeTest(Method method) {}
#Override
public void prepareTest(Object test) {
TestApplication application = (TestApplication) Robolectric.application;
RoboGuice.setBaseApplicationInjector(application, RoboGuice.DEFAULT_STAGE,
RoboGuice.newDefaultRoboModule(application), new TestModule());
RoboGuice.getInjector(application).injectMembers(test);
}
#Override
public void afterTest(Method method) {}
}
As this class has Test at the start robolectric should automatically find it and use it. However this doesn't seem to be happening. Does anybody know why?
UPDATE 2
This blog would suggest that the testmodule needs to be in the same package however I have all tests in a different package. How do I work around this?
Your TestApplication class should extend your own Application class, not android.app.Application, and it should be in the same package as your Application.
... however I have all tests in a different package.
That shouldn't be a problem. Put your TestApplication in your test module, but use the package from Application.
e.g., if you're using maven, the files would live here:
src/main/java/com/example/Application.java
src/test/java/com/example/TestApplication.java

How to call testcase class inside other testcase class?

I have problem with creating JUnit Test Automation.
My project has many activities (some activities inside other activities).
So I create testcase for each activity. But the problem how can i call a testcase inside other testcases (like activity inside other activities).
Can any one give me some idear?
Thanks.....
You tests should live in a different project not with your Activities.
Then the test runner, usually InstrumentationTestRunner, will be able to discover and run your test cases using instrospection.
Disclaimer: this can get very, very messy. If you need one test case to spawn another test case, there's probably a better way of doing it.
JUnit operates on classes. If you want to create tests at runtime, you have to create classes at runtime. Here, the method specializedTester creates an anonymous subclass where getInstance() returns specialized Activity objects for testing.
public abstract class ActivityTestCase extends TestCase {
public abstract Activity getInstance();
public static Class specializedTester(final String specialty) {
return new ActivityTestCase() {
public Activity getInstance() {
return new Activity(specialty);
}
};
}
public void testChildActivities() {
Activity activity = getInstance();
for(Activity a : activity.children()) {
// "check ripeness", "bargain hunt", "check out", etc
Class c = specializedTester(a.specialty);
suite.addTestSuite(c);
}
}
static TestSuite suite;
public static void main(String[] args) {
suite = new TestSuite(ActivityTestCase.specializedTester("buy groceries"));
TestRunner.run(suite);
}
}

Categories

Resources