I have a need to use a different implementation class for an interface when I'm testing an Android Activity than when it runs in production. I'm familiar with using mock objects, but the problem is that an Activity on Android is instantiated by the OS and not by me, so by the time I get a reference to the Activity, it has already been instantiated and already has used the implementation class. In my case, the class I'm talking about is a Login class. For my automated tests, I want to be able to control the login result. Here is my example:
class MainActivity extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.main);
LoginMgr aLoginMgr = new LoginMgrImpl();
if (aLoginMgr.authenticated()) {
//do something
} else {
//do something else
}
}
}
public interface LoginMgr {
public boolean authenticated();
}
Elsewhere in a library I have an implementation class:
public class LoginMgrImpl implements LoginMgr {
//authenticate
}
During an automated test, I want to be able to replace the production LoginMgrImpl
with a test version that will return the boolean I need for my test. This is where I need help as I cannot see how I can get the MainActivity
to use a different LoginMgrImpl
. I'm using Eclipse if that matters. The ActivityInstrumentationTestCase2<MainActivity>
test class creates the MainActivity
for me when I call getActivity(). It calls the no arg constructor, so I don't have a chance to change LoginMgrImpl
there. Eclipse controls the build, so I don't see a way to have MainActivity
built with a different implementation library.
Can any of you more experienced folks point me in a direction to successfully automate my test? I can't be the only one trying to mock some test classes used by Activity objects, but I've spent hours trying to find a solution in the forums with no success. HELP!
Based on feedback from everyone, I tried various solutions and found two that I can live with.
SOLUTION 1: One is explained in the marked answer and involves setting up a separate "Project" in Eclipse that has symbolic links to the original src, res, assets folders as well as to the AndroidManifest.xml
. I can then modify the project properties for the linked version of the project to reference a different library implementation that supports my test. This works, but feels kludgy.
SOLUTION 2: Define an Application subclass for my project which keeps a singleton instance of the implementation of LoginMgr
. The MainActivity
will then retrieve the LoginMgr
from the Application instance. During testing I can use ActivityUnitTestCase<MainActivity>
to inject a mock Application that references a mock LoginMgr. Here are the code snippets:
Main Application: (be sure AndroidManifest.xml
includes <Application android:name="MainApplication" ...>
)
class MainApplication extends Application {
private static LoginMgr sLoginMgr = new LoginMgrImpl();
public LoginMgr getLoginMgr() {
return sLoginMgr;
}
}
MainActivity has to be modified as:
class MainActivity extends Activity {
public void onCreate(Bundle savedState) {
super.onCreate(savedState);
setContentView(R.layout.main);
// 2 lines added
MainApplication aApplication = (MainApplication)getApplication();
LoginMgr aLoginMgr = aApplication.getLoginMgr();
if (aLoginMgr.authenticated()) {
//do something
} else {
//do something else
}
}
}
The unit test must be something like this:
public class MainActivityTest extends ActivityUnitTestCase<MainActivity> {
public MainActivityTest() {
super(MainActivity.class);
}
public void testUseAlternateLoginMgr() {
MainApplication aMockApplication = new MainApplication()
{
@Override
public LoginMgr getLoginMgr() {
return new LoginMgr() {
public boolean authenticated() { {
return true;
}
};
}
};
setApplication(aMockApplication);
Intent aDependencyIntent = new Intent("android.intent.action.MAIN");
Activity aMainActivity = startActivity(aDependencyIntent, null, null);
// verify results
}
}
I would probably use a mock framework instead of a hand coded mock, but this is just an illustration.
I think solution 2 is the better way to go. Thanks @yorkw!