How to unit test interaction between FragmentDialog and Activity which implements interface - android

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.

Related

How do I mock a class with expresso in android?

Hello I am struggling with a task that seems very simple. I need to write an expresso unit test but I need the activity to use a mocked class instead of the one it uses during the normal run.
the, the closest solution I saw was here:
Nullifying or overriding API calls made in an Activity under an Espresso test
But this requires getters and setters at the application level and only deals with constants.
Here is a simple example of want to do:
Activity:
public class MainActivity2 extends AppCompatActivity {
// how do I mock this without using any branches
// just have expresso replace with MainActivity2CustomClass with a mock?
private MainActivity2CustomClass mainActivity2CustomClass;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mainActivity2CustomClass = new MainActivity2CustomClass();
mainActivity2CustomClass.foo();
}
}
the class I want to mock
// Need expresso to replace this class with a mock
public class MainActivity2CustomClass {
void foo() {
}
}
To clarify I need to do this with a large application with several classes. So using branches is NOT the solution I seek. For instance this will not work for my application
// I DO ***NOT** WANT THIS solution since it will explode in many branches in my application
Intent intent = new Intent(targetContext, PatientActivity.class);
intent.putExtra("istestrunning", 2);
// etc...
// Not acceptable solution for my problem, creates too many branches and messy code
if (getIntent().getIntExtra("istestrunning", 0) == 2) {
mainActivity2CustomClass = new MainActivity2CustomClass();
mainActivity2CustomClass.foo();
} else {
mainActivity2CustomClass2 = new MyMock();
mainActivity2CustomClass2.foo();
}
I want to know if it possible to have expresso simply replace that class at testing time. Can it be done?
Any help or leads is appreciated.
thank you.

Getting instance of MainActivity

Hi I am kind of new to android, still learning. And my problem is that, for example I have a method which was created in the MainActivity and I need to call it from another class.
Is it a good practice to get the instance of the MainActivity so that I may be able to call the method in the MainActivity from another class?
This is an example:
public class MainActivity extends AppCompatActivity {
private static MainActivity inst;
public static MainActivity instances()
{
return inst;
}
#Override
public void onStart() {
super.onStart();
inst = this;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void showToast (String text){
Toast.makeText(inst, text, Toast.LENGTH_SHORT).show();
}
Then this is the other class:
public class broadcastReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
MainActivity instance = new MainActivity();
instance.showToast(AnyText);
}
}
I saw this type of coding while looking at tutorials and wondered if it's a good practice or maybe there might be a better way? Since I get the warning of Do not place Android Context Classes in static classes
Thanks in advance for any insight or help! :D
I guess You want to make A singleton of Activity Class
but as Mention in All Pattern Design
using Singleton
If and Only If its only way to Make A Global Variable
Singleton is based on Lazing Initialing and Load On Memory
so I guess If you cant to Interact With Activiy You can Use
BroadCast Or Intents
You can call method from another class like this:
MainActivity instance = new MainActivity();
String data = instance.data();
and create data method in that class:
public String data() {
return mangaId;
}
Is it a good practice to get the instance of the MainActivity so that
I may be able to call the method in the MainActivity from another
class?
You totally can do this but you don't need to make it static and use a constructor. Just create a new instance like follows and you'll access the public methods
MainActivity mainActivity = new MainActivity();
mainActivity.showToast(text);
About the warning
It suggests avoiding having context fields defined as static. The warning itself explains why: It's a memory leak. If you make it static it will be accessible anywhere in your app and some methods can hold the reference to this context for a really long time and it won't be garbage collected. It will lead to a outofmemory exception and the app could crash. But here you're trying to invoke showToast() from broadcastreceiver so you can just get rid of static references. And it you need them in the future you safe ways to inject context
You cannot create instances of an Activity using the new operator.
You have to use an Intent to let an Activity to be created.
So you cannot get a reference to an instance of your activity.
The only methods you can use of your activity class are static ones.

How do I make my activity use testing data?

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;
}
};
}

How do I abstract away dependencies in Android library code?

Here is my scenario.
I have an android activity in which I want to abstract my I/O dependencies. The dependencies are represented by this interface (edited for brevity and simplicity):
public interface ITimeDataServer {
TimeRecord[] get(int userID);
void save(TimeRecord record);
}
What I want is for my activity to be able to call these interface methods, and leave the implementation to be supplied by the calling code. (Pretty standard, I think).
ITimeDataServer myServer;
int myUserID;
void loadRecords() {
TimeRecord[] records = myServer.get(myUserID);
// etc...
}
My difficulty is, how can I ensure that myServer gets set?
This seems like a common problem, but I can't find a clean solution.
My first thought would be that myServer would be passed in through the constructor, but Android activities aren't really instantiated with constructors.
I've come up with several solutions, but they're all icky in some way:
Icky Solution 1
Create a static method to launch the activity class which takes an ITimeDataServer parameter and stores it in a static variable from which the activity can access it:
private static ITimeDataSource theDataSource;
public static void launch(Activity currentActivity, ITimeDataSource dataSource) {
theDataSource = dataSource;
Intent intent = new Intent(currentActivity, MainActivity.class);
currentActivity.startActivity(intent);
}
This is icky because (a) the data source is static and not actually associated with the instance, and (b) a consumer could initiate the activity by the standard activity API rather than this static method, which will cause NullPointerException.
Icky Solution 2
I can create a Provider class which provides a singleton instance of ITimeDataSource, which needs to be initialized by the calling library before use:
public class TimeDataSourceProvider {
private static ITimeDataSource myDataSource = null;
public void initialize(ITimeDataSource dataSource) {
myDataSource = dataSource;
}
public ITimeDataSource get() {
if (myDataSource == null)
throw new NullPointerException("TimeDataSourceProvider.initialize() must be called before .get() can be used.");
else
return myDataSource;
}
}
This seems a little less icky, but it's still a little icky because the activity's dependency is not obvious, and since there may be many paths to launch it, it's highly possible that some of them would forget to call TimeDataSourceProvider.initialize().
Icky solution 3
As a variation on #2, create a static IODependencyProvider class which must be initialized with ALL dependencies on app startup.
public class IODependencyProvider {
static ITimeDataSource myTimeData;
static IScheduleDataSource myScheduleData; // etc
public static void initialize(ITimeDataSource timeData, IScheduleDataSource scheduleData /* etc */) {
myTimeData = timeData;
myScheduleData = scheduleData;
//etc
}
public static ITimeDataSource getTimeData() {
if (myTimeData == null)
throw new NullPointerException("IODependencyProvider.initialize() must be called before the getX() methods can be used.");
else
return myTimeData;
}
// getScheduleData(), etc
}
This seems superior to #1 and #2 since a failure to initialize would be much harder to sneak by, but it also creates interdependencies among the data types that otherwise need not exist.
...and other icky variations on that theme.
The common themes that make these solutions crappy:
the need to use static fields to pass non-serializable information to an activity
the lack of ability to enforce initialization of those static fields (and subsequent haphazardness)
inability to clearly identify an activity's dependencies (due to reliance on statics)
What's a nooby Android developer to do?
As long as these dependencies implement Parcelable correctly, you should be able to add them to your intent, then unparcel them as ITimeDataServer and get the correct class.
I found a nice solution here, in the least-loved answer.
I define the library activity as abstract and with no default constructor, but a constructor that takes an interface, like so:
public abstract class TimeActivity extends AppCompatActivity {
private ITimeDataSource myTimeDataSource;
public TimeActivity(#NonNull ITimeDataSource dataSource) {
myTimeDataSource = dataSource;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_time);
// do stuff with myTimeDataSource!
}
}
Then, the calling code can create a concrete subclass with its chosen implementation that does have a parameterless constructor. No static members, easy-peasy!
This allows you to abstract and inject all sorts of crazy behaviours! Woooo!
(Note that the concrete subclass activity needs to be manually added to AndroidManifest.xml, like all activities, or the app will crash when it tries to launch.)

Is this correct design approach for splitting logic between activity and other class

I have an android/programming design question. For some reason I feel my design is awkward but if it is correct (no issues) then I would like a confirmation so I know I am on the right path.
Often I come across where I have Activity (lets say it has map). I don't want to put everything in this class ( ie map work). I usually create the activity class and another class (say map worker). I pass the activity as a reference to the map worker class, so that map worker class can method in there. Something like that
public class MainActivity extends Activity{
public void onCreate(){
MapWorker myWorker = new MapWorker(this);
}
public renderUpdate (Marker m){
mapView.addMarker(m)
}
}
The map worker class is as follows
public class MapWorker {
MainActivity act;
public void MapWorker(MainActivity act){
this.act = act;
}
public doWork(){
//do lots of work
.
.
.
act.renderUpdate(marker)
}
}
These classes have two way linking as you see. Is this ok or should I do it differently?and Why?
Thank you so much

Categories

Resources