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);
}
}
Related
I have an application which displays data (posts) from a web API.
A background service syncs this data at some unknown time and saves it.
When visiting my main activity it loads this data and displays it in a RecyclerView
The loading is handled via a singleton class
I currently test the main activity as follows
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
#Test
public void testDataLoad() {
int postsTotal = DataSingleton.getInstance().getPostsCount();
ViewInteraction empty = onView(withId(R.id.empty_view));
ViewInteraction recycler = onView(withId(R.id.recycler_view));
if (postsTotal == 0) {
empty.check(matches(isDisplayed()));
recycler.check(matches(not(isDisplayed())));
} else {
empty.check(matches(not(isDisplayed())));
recycler.check(matches(isDisplayed()));
recycler.check(new RecyclerViewItemCountAssertion(greaterThan(postsTotal)));
}
}
I know that this can't be the right way to write tests. I want to be able to test both with an empty data set and a non-empty set so that the if-else is two separate tests. The only way I think I can achieve it is to mock the data.
Is there another way?
Can I use Mockito to make the MainActivity use mock data without modifying the production code? Is my only choice to make it inject either real or mocked data providers in place of my singleton?
Is it better to just uninstall and reinstall my app each time so there is no data to start with and then continue with real data testing?
Android Activity are heavyweight and hard to test. Because we don't have control over the constructor, it is hard to swap in test doubles.
The first thing to do is to make sure you are depending on an abstraction of the data-source rather than a concretion. So if you are using a singleton with a getPostsCount() method then extract an interface:
interface DataSourceAbstraction {
int getPostsCount();
}
Make a wrapper class that implements your interface:
class ConcreteDataSource implements DataSourceAbstraction {
#Override
int getPostsCount() {
return DataSingleton.getInstance().getPostsCount();
}
}
And make the Activity depend on that rather than the concrete DataSingleton
DataSourceAbstraction dataSourceAbstraction;
#Override
protected void onCreate(Bundle savedInstanceState) {
super(savedInstanceState);
injectMembers();
}
#VisibleForTesting
void injectMembers() {
dataSourceAbstraction = new ConcreteDataSource();
}
You can now swap in a test double by subclassing and overriding injectMembers that has relaxed visibility. It's a bad idea do this in enterprise development, but there are less options in Android Activities where you don't control the constructor of the class.
You can now write:
DataSourceAbstraction dataSource;
//system under test
MainActivity mainActivity
#Before
public void setUp() {
mockDataSource = Mockito.mock(DataSourceAbstraction.class);
mainActivity = new MainActivity() {
#Override
void injectMembers() {
dataSourceAbstraction = mockDataSource;
}
};
}
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've recently gone whole-hog with Dagger because the concept of DI makes complete sense. One of the nicer "by-products" of DI (as Jake Wharton put in one of his presentations) is easier testability.
So now I'm basically using Espresso to do some functional testing, and I want to be able to inject dummy/mock data to the application and have the activity show them up. I'm guessing since, this is one of the biggest advantages of DI, this should be a relatively simple ask. For some reason though, I can't seem to wrap my head around it. Any help would be much appreciated. Here's what I have so far (I've written up an example that reflects my current setup):
public class MyActivity
extends MyBaseActivity {
#Inject Navigator _navigator;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.get(this).inject(this);
// ...
setupViews();
}
private void setupViews() {
myTextView.setText(getMyLabel());
}
public String getMyLabel() {
return _navigator.getSpecialText(); // "Special Text"
}
}
These are my dagger modules:
// Navigation Module
#Module(library = true)
public class NavigationModule {
private Navigator _nav;
#Provides
#Singleton
Navigator provideANavigator() {
if (_nav == null) {
_nav = new Navigator();
}
return _nav;
}
}
// App level module
#Module(
includes = { SessionModule.class, NavigationModule.class },
injects = { MyApplication.class,
MyActivity.class,
// ...
})
public class App {
private final Context _appContext;
AppModule(Context appContext) {
_appContext = appContext;
}
// ...
}
In my Espresso Test, I'm trying to insert a mock module like so:
public class MyActivityTest
extends ActivityInstrumentationTestCase2<MyActivity> {
public MyActivityTest() {
super(MyActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
ObjectGraph og = ((MyApplication) getActivity().getApplication()).getObjectGraph().plus(new TestNavigationModule());
og.inject(getActivity());
}
public void test_SeeSpecialText() {
onView(withId(R.id.my_text_view)).check(matches(withText(
"Special Dummy Text")));
}
#Module(includes = NavigationModule.class,
injects = { MyActivityTest.class, MyActivity.class },
overrides = true,
library = true)
static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator() {
return new DummyNavigator(); // that returns "Special Dummy Text"
}
}
}
This is not working at all. My Espresso tests run, but the TestNavigationModule is completely ignored... arr... :(
What am I doing wrong? Is there a better approach to mocking modules out with Espresso? I've searched and seen examples of Robolectric, Mockito etc. being used. But I just want pure Espresso tests and need to swap out a module with my mock one. How should i be doing this?
EDIT:
So I went with #user3399328 approach of having a static test module list definition, checking for null and then adding it in my Application class. I'm still not getting my Test injected version of the class though. I have a feeling though, its probably something wrong with dagger test module definition, and not my espresso lifecycle. The reason I'm making the assumption is that I add debug statements and find that the static test module is non-empty at time of injection in the application class. Could you point me to a direction of what I could possibly be doing wrong. Here are code snippets of my definitions:
MyApplication:
#Override
public void onCreate() {
// ...
mObjectGraph = ObjectGraph.create(Modules.list(this));
// ...
}
Modules:
public class Modules {
public static List<Object> _testModules = null;
public static Object[] list(MyApplication app) {
// return new Object[]{ new AppModule(app) };
List<Object> modules = new ArrayList<Object>();
modules.add(new AppModule(app));
if (_testModules == null) {
Log.d("No test modules");
} else {
Log.d("Test modules found");
}
if (_testModules != null) {
modules.addAll(_testModules);
}
return modules.toArray();
}
}
Modified test module within my test class:
#Module(overrides = true, library = true)
public static class TestNavigationModule {
#Provides
#Singleton
Navigator provideANavigator()() {
Navigator navigator = new Navigator();
navigator.setSpecialText("Dummy Text");
return navigator;
}
}
With Dagger 2 and Espresso 2 things have indeed improved. This is how a test case could look like now. Notice that ContributorsModel is provided by Dagger. The full demo available here: https://github.com/pmellaaho/RxApp
#RunWith(AndroidJUnit4.class)
public class MainActivityTest {
ContributorsModel mModel;
#Singleton
#Component(modules = MockNetworkModule.class)
public interface MockNetworkComponent extends RxApp.NetworkComponent {
}
#Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(
MainActivity.class,
true, // initialTouchMode
false); // launchActivity.
#Before
public void setUp() {
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
RxApp app = (RxApp) instrumentation.getTargetContext()
.getApplicationContext();
MockNetworkComponent testComponent = DaggerMainActivityTest_MockNetworkComponent.builder()
.mockNetworkModule(new MockNetworkModule())
.build();
app.setComponent(testComponent);
mModel = testComponent.contributorsModel();
}
#Test
public void listWithTwoContributors() {
// GIVEN
List<Contributor> tmpList = new ArrayList<>();
tmpList.add(new Contributor("Jesse", 600));
tmpList.add(new Contributor("Jake", 200));
Observable<List<Contributor>> testObservable = Observable.just(tmpList);
Mockito.when(mModel.getContributors(anyString(), anyString()))
.thenReturn(testObservable);
// WHEN
mActivityRule.launchActivity(new Intent());
onView(withId(R.id.startBtn)).perform(click());
// THEN
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("Jesse"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 0))
.check(matches(hasDescendant(withText("600"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("Jake"))));
onView(ViewMatchers.nthChildOf(withId(R.id.recyclerView), 1))
.check(matches(hasDescendant(withText("200"))));
}
Your approach doesn't work because it only happens once, and as Matt mentioned, when the activity's real injection code runs, it will wipe out any variables injected by your special object graph.
There are two ways to get this to work.
The quick way: make a public static variable in your activity so a test can assign an override module and have the actual activity code always include this module if it's not null (which will only happen in tests). It's similar to my answer here just for your activity base class instead of application.
The longer, probably better way: refactor your code so that all activity injection (and more importantly graph creation) happens in one class, something like ActivityInjectHelper. In your test package, create another class named ActivityInjectHelper with the exact same package path that implements the same methods, except also plusses your test modules. Because test classes are loaded first, your application will execute with the testing ActivityInjectHelper. Again it's similar to my answer here just for a different class.
UPDATE:
I see you've posted more code and it's close to working, but no cigar. For both activities and applications, the test module needs to be snuck in before onCreate() runs. When dealing with activity object graphs, anytime before the test's getActivity() is fine. When dealing with applications, it's a bit harder because onCreate() has already been called by the time setUp() runs. Luckily, doing it in the test's constructor works - the application hasn't been created at that point. I briefly mention this in my first link.
The call to getActivity will actually start your activity calling onCreate in the process which means you won't be getting your test modules added to the graph in time to be used. Using activityInstrumentationTestcase2 you can't really inject properly at the activity scope. I've worked around this by using my application to provide dependencies to my activities and then inject mock objects into it which the activities will use. It's not ideal but it works. You can use an event bus like Otto to help provide dependencies.
EDIT: the below in post form http://systemdotrun.blogspot.co.uk/2014/11/android-testing-with-dagger-retrofit.html
To test an Activity using Espresso + Dagger I have done the below
Inspired by the answer from #user3399328 I have a DaggerHelper class inside my Application class, which allows the test case to override the #Providers using Test #Modules which supply mocks. As long as
1) This is done before the testCases getActivity() call is made (as my inject call happens in my activity inside Activity.onCreate)
2) tearDown removes the test modules from the object graph.
Examples below.
Note: this is not ideal as this is subject to similar pitfalls of using factory methods for IoC but at least this way its only ever a single call in tearDown() to bring the system under test back to normal.
The DaggerHelper inside my Application class
public static class DaggerHelper
{
private static ObjectGraph sObjectGraph;
private static final List<Object> productionModules;
static
{
productionModules = new ArrayList<Object>();
productionModules.add(new DefaultModule());
}
/**
* Init the dagger object graph with production modules
*/
public static void initProductionModules()
{
initWithModules(productionModules);
}
/**
* If passing in test modules make sure to override = true in the #Module annotation
*/
public static void initWithTestModules(Object... testModules)
{
initWithModules(getModulesAsList(testModules));
}
private static void initWithModules(List<Object> modules)
{
sObjectGraph = ObjectGraph.create(modules.toArray());
}
private static List<Object> getModulesAsList(Object... extraModules)
{
List<Object> allModules = new ArrayList<Object>();
allModules.addAll(productionModules);
allModules.addAll(Arrays.asList(extraModules));
return allModules;
}
/**
* Dagger convenience method - will inject the fields of the passed in object
*/
public static void inject(Object object) {
sObjectGraph.inject(object);
}
}
My Test module inside my test class
#Module (
overrides = true,
injects = ActivityUnderTest.class
)
static class TestDataPersisterModule {
#Provides
#Singleton
DataPersister provideMockDataPersister() {
return new DataPersister(){
#Override
public void persistDose()
{
throw new RuntimeException("Mock DI!"); //just a test to see if being called
}
};
}
}
Test method
public void testSomething()
{
MyApp.DaggerHelper.initWithTestModules(new TestDataPersisterModule());
getActivity();
...
}
Tear down
#Override
public void tearDown() throws Exception
{
super.tearDown();
//reset
MyApp.DaggerHelper.initProductionModules();
}
I need to determine in runtime from code if the application is run under TestInstrumentation.
I could initialize the test environment with some env/system variable, but Eclipse ADK launch configuration would not allow me to do that.
Default Android system properties and environment do not to have any data about it. Moreover, they are identically same, whether the application is started regularly or under test.
This one could be a solution: Is it possible to find out if an Android application runs as part of an instrumentation test but since I do not test activities, all proposed methods there won't work. The ActivityManager.isRunningInTestHarness() method uses this under the hood:
SystemProperties.getBoolean("ro.test_harness")
which always returns false in my case. (To work with the hidden android.os.SystemProperties class I use reflection).
What else can I do to try to determine from inside the application if it's under test?
I have found one hacky solution: out of the application one can try to load a class from the testing package. The appication classloader surprisingly can load classes by name from the testing project if it was run under test. In other case the class is not found.
private static boolean isTestMode() {
boolean result;
try {
application.getClassLoader().loadClass("foo.bar.test.SomeTest");
// alternatively (see the comment below):
// Class.forName("foo.bar.test.SomeTest");
result = true;
} catch (final Exception e) {
result = false;
}
return result;
}
I admit this is not elegant but it works. Will be grateful for the proper solution.
The isTestMode() solution did not work for me on Android Studio 1.2.1.1. Almighty Krzysztof from our company tweaked your method by using:
Class.forName("foo.bar.test.SomeTest");
instead of getClassLoader(). Thanks for Krzysztof!
We created a solution to pass parameters to the MainActivity and use it inside the onCreate method, enabling you to define how the Activity will be created.
In MainActivity class, we created some constants, which could also be an enum. We created a static attribute too.
public class MainActivity {
public static final int APPLICATION_MODE = 5;
public static final int UNIT_TEST_MODE = 10;
public static final int OTHER_MODE = 15;
public static int activityMode = APPLICATION_MODE;
(...)
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
switch (activityMode) {
case OTHER_MODE:
(...)
break;
case UNIT_TEST_MODE:
Log.d(TAG, "Is in Test Mode!");
break;
case APPLICATION_MODE:
(...)
break;
}
(...)
}
(...)
}
We made MainActivityTest class abstract, created a setApplicationMode and called this method inside the setUp() method, before calling the super.setUp() method.
public abstract class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
protected void setUp() throws Exception {
setApplicationMode(); // <=====
super.setUp();
getActivity();
(...)
}
(...)
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.UNIT_TEST_MODE;
}
}
All other test classes inherit from MainActivityTest, if we want it to have another behaviour, we can simply override the setApplicationMode method.
public class OtherMainActivityTest extends MainActivityTest {
(...)
#Override
public void setApplicationMode() {
MainActivity.activityMode = MainActivity.OTHER_MODE;
}
}
The user nathan-almeida is the friend that is co-author of this solution.
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 {
}
}