Robolectric: how to test class which use application instance inside? - android

I want to test a fragment UserConnectFragment which contains a variable PlateformConnect. This class has a method to initialise Facebook SDK:
#Override
public void create() {
FacebookSdk.sdkInitialize(MyApplication.getInstance().getApplicationContext());
}
I extended Android application with MyApplication class.
In UserConnectFragment, I use PlateformConnect like that:
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Must be done before the content view assignement!
PlateformConnect.getInstance().create();
...
In my Robolectric class test:
#Before
public void setUp() throws Exception {
// Create basic activity, and add fragment
mActivity = Robolectric.buildActivity(FragmentActivity.class).create().start().resume().get();
mUserConnectFragment = new UserConnectFragment();
addMapFragment(mActivity, mUserConnectFragment);
//mLoginButton = (Button) mActivity.findViewById(R.id.facebook_button);
}
There is a crash when this test runs:
java.lang.NullPointerException
at com.xxx.yyyy.ui.intro.UserConnectFragment.onViewCreated(UserConnectFragment.java:77)
And this error appears because I use:
MyApplication.getInstance().getApplicationContext()
... and getInstance() returns null.
In my application I use MyApplication.getInstance() in a lot of class, so how can I do to test with Robolectric ??
Thanks guys!

I found the solution: just add #Config(xxx) to set the Application class name.
#RunWith(RobolectricTestRunner.class)
#Config(application = MyApplication.class)
public class UserConnectFragmentTest {
...
More details here: http://robolectric.org/custom-test-runner/

ApplicationProvider.getApplicationContext() worked for me.

Related

Android UI testing with mocked dependencies

I have an activity in which I provide a button. Clicking on the button invokes a method in a data provider class and based on the return value of the method I make UI changes. Now I want to write an instrumented test where I perform click() in the button but avoid actually calling the method in the data provider class. Instead I want to return a desired value from the method and then check if the UI was modified accordingly.
MyActivity
#Override
public void onCreate(final Bundle savedInstanceState) {
mActionButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(final View v) {
boolean result = dataProvider.getResult();
if(result) {
mSuccessTextView.setVisibility(View.VISIBLE);
}
}
});
}
Here, on button click, a call is made to DataProvider#getResult and the result from this method is stored in result. If the result is true a TextView mSuccessTextView, previously GONE, is now made VISIBLE.
The problem here is DataProvider#getResult deals with a lot of external components that would make testing impossible. So what I want to do is use a mocked instance of DataProvider so that I can get getResult to return a desired value and then check the visibility of mSuccessTextView. This is what I tried :
MyActivityTest.java
#RunWith(AndroidJUnit4.class)
public class MyActivityTest {
private DataProvider mDataProvider;
#Rule
public IntentsTestRule<MyActivity> mIntentRule =
new IntentsTestRule<>(MyClientActivity.class);
#Before
public void setUp() {
mDataProvider = mock(DataProvider.class);
}
#Test
public void testResultSuccess() {
boolean result = true;
when(mDataProvider.getResult()).thenReturn(result);
onView(withId(R.id.action_button)).perform(click());
onView(withId(R.id.success_text_view)).check((ViewAssertion) isDisplayed());
}
}
Doing the above generates the following error :
org.mockito.exceptions.base.MockitoException:
Mockito cannot mock this class: class com.domain.myapp.DataProvider.
Mockito can only mock non-private & non-final classes.
If you're not sure why you're getting this error, please report to the mailing list.
Underlying exception : java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.UnsupportedOperationException: Cannot define class using reflection
.
.
.
Caused by: java.lang.IllegalStateException: This JVM's version string does not seem to be valid: 0
.
.
.
Even if you could mock DataProvider, it would not help you because you are not injecting its instance into MyClientActivity during your test. Reasons for not able to mock DataProvider are unknown, pls provide the class.

Robolectric 3.8 with MVP and mocking setupActivity()

I use Android MVP architecture and I want to test my View. I have the following code in my project:
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
#Test
public void testDisplay() throws Exception {
MainActivity activity = Robolectric.setupActivity(MainActivity.class);
final Menu menu = Shadows.shadowOf(activity).getOptionsMenu();
assertEquals("Log", menu.findItem(R.id.menu_login).getTitle().toString());
}
}
However, I get ExceptionInIntializationError for some 3rd party library throws it in the Activity's onResume() step which is invoked by Robolectric.
Moreover, in Activity's onResume() the Presenter's method is called which makes a network request.
My questions are:
How to mock 3rd party intialization step? I read about Shadows, but I don't know how to use them in this particular scenario. I should mock stuff during setupActivity(...) call.
How to mock the network call? In my MVP code, the presenter is created in onCreate() method. Should I switch to direct dependency injection pattern and rebuild the architecture? But this approach would conflict with Robolectric.setupActivity(MainActivity.class); as I need some kind of hook to inject stuff in onCreate() or onResume() during Robolectric calls.
Ok, I came up with the following solution. It is strange that I have never seen similar examples on their page or in articles. I have used Mockito along with Robolectric.
Firstly, I have added setPresenter method to Activity(MVP's View) to insert custom-mock Presenter into View
#VisibleForTesting
public void setPresenter(P presenter) {
this.presenter = presenter;
}
In my code, Presenter is created during onCreate() method, so I have added null-check in order to inject Presenter before onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (presenter == null)
presenter = providePresenter(App.appComponentFrom(this));
presenter.create();
}
In Robolectric framework I have found buildActivity() method which just invokes Activity's constructor (no lifecycle methods!).
Then after this call I injected my mock-Presenter.
The last step is to call activityController.setup().get() as is does the same job as setupActivity(), which is calling lifecycle methods: create(), resume()
#RunWith(RobolectricTestRunner.class)
#Config(constants = BuildConfig.class, sdk = 21)
public class MainActivityTest {
#Mock
CheckoutContract.Presenter presenter;
private ShadowActivity mainActivity;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ActivityController<MainActivity> activityController = Robolectric.buildActivity(MainActivity.class);
activityController.get().setPresenter(presenter);
mainActivity = Shadows.shadowOf(activityController.setup().get());
}
#Test
public void checkText() throws Exception {
assertEquals(RuntimeEnvironment.application.getString(R.string.activityMain_title),
((Toolbar)checkoutActivity.findViewById(R.id.vToolbar)).getTitle());
}
}
Voila! In my test I got Context Resources from RuntimeEnvironment and it have passed!

Mock activity class member before onCreate

I am trying to test an activity using robolectric 3.3.2.
Want to mock and activity's member initialization as the direct call results in NPE.
ActivityController<MyActivity> activityController =
Robolectric.buildActivity(MyActivity.class);
mTestActivity = activityController.get();
Mockito.when(mTestActivity.getCountry()).thenReturn("xxxx");
activityController.setup();
Tried out above code, but the setup.() (oncreate) ignores the mock of
getCountry method and invokes the definition from activity.
Is there a way to achieve this?
It will not work like this even if you use spies (#Spy, Mockito.spy()).
You should use stub:
public class MyActivityTest{
public static class StubMyActivity extends MyActivity {
Country getCountry() {
return someSpecialCountry;
}
}
#Before
public void setUp(){
ActivityController<StubMyActivity> activityController =
Robolectric.buildActivity(StubMyActivity.class);
mTestActivity = activityController.setup().get();
}
}

Unable to find resource ID when building fragment with Robolectric

Using Robolectric 3.3.2 I am trying to build a fragment and attach it to an activity. I tried Robolectric.buildFragment(MyFragment.class, MyActivity.class).create() but I get exception android.content.res.Resources$NotFoundException: Unable to find resource ID #0x1 in packages [android, com.my.package.for.android].
I also tried building the activity and fragment separately and attach the fragment using the fragment manager but it seems that the fragment is automatically attached to a dummy activity so I cannot attach it to my activity.
Minimalistic example:
#RunWith(RobolectricTestRunner.class)
#Config(sdk = Build.VERSION_CODES.KITKAT, manifest = "./src/main/AndroidManifest.xml")
public class MyTest {
#Test
public void WorkingTest1() throws Exception {
Robolectric.buildFragment(MyFragment.class).create();
}
#Test
public void WorkingTest2() throws Exception {
Robolectric.buildActivity(MyActivity.class).create();
}
#Test
public void FailingTest() throws Exception {
Robolectric.buildFragment(MyFragment.class, MyActivity.class).create();
}
public static class MyActivity extends Activity {
}
public static class MyFragment extends Fragment {
}
}
I have a root Activity frame already defined for my Activity, so I can supply that for the FragmentManager transaction inside Robolectric's FragmentController:
Robolectric
.buildFragment(MyFragment.class, MyActivity.class)
.create(R.id.my_activity_frame);
In my Activity:
setContentView(R.id.my_activity_frame);
Upon further investigation I did find a workaround (albeit ugly). It seems Robolectric expects a ViewGroup with id 1 in the activity view hierarchy. The solution I found was to write findViewById(android.R.id.content).setId(1); in the activity's onCreate(Bundle) (or add a new ViewGroup and set it's id to 1 programmatically). This seems like a terrible thing to to in production code though so I made a test subclass.
public class MyTestableActivity extends MyActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findViewById(android.R.id.content).setId(1);
}
}
If you are running with default RobolectricTestRunner , make sure your res/ folder is at the same location as AndroidManifest.xml.
I got the issue resolved with something like
#RunWith(RobolectricTestRunner.class)
#Config(manifest = "./path/AndroidManifest.xml")
public class FragmentTest{
}
I resolved this by doing a variation of other answers:
Robolectric
.buildFragment(YourFragment.class, Activity.class)
.create(android.R.id.content, new Bundle());
Note that you can also substitute Activity.class for a static inner test-only YourTestActivity.class as well.

Android: Writing test cases for Fragments

In my previous projects I've done most of the work through Activities and used ActivityInstrumentationTestCase2 as per the document:
http://developer.android.com/tools/testing/activity_testing.html
I have an idea how to work with Activity Test cases; but when it comes to Fragment ,I don't have much idea nor found much documents related to that.
So how to write test cases when I have several fragments with one or two actvities?
Any example code or sample would be more helpful.
Here's a rough guide using ActivityInstrumentationTestCase2:
Step 1. Create a blank Activity to hold your fragment(s)
private static class FragmentUtilActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout view = new LinearLayout(this);
view.setId(1);
setContentView(view);
}
}
Step 2:
Inside your test, instantiate your fragment and add it to the blank activity
public class MyFragmentTest extends ActivityInstrumentationTestCase2<FragmentUtilActivity> {
private MyFragment fragment;
#Before
public void setup() {
fragment = new MyFragment();
getActivity().getFragmentManager().beginTransaction().add(1, fragment, null).commit();
}
}
Step 3 Test your instantiated fragment
#Test
public void aTest() {
fragment.getView().findViewById(...);
}
If you're using robolectric, this is pretty straightforward using the FragmentUtilTest class:
#Test
public void aTest() {
// instantiate your fragment
MyFragment fragment = new MyFragment();
// Add it to a blank activity
FragmentTestUtil.startVisibleFragment(fragment);
// ... call getView().findViewById() on your fragment
}
Here is my working solution:
Create an instrumentation unit test class for this in androidTest directory, i.e.:
public class FragmentTest extends
ActivityInstrumentationTestCase2<MainActivity> {
private MainActivity testingActivity;
private TestFragment testFragment;
//...
}
call this constructor inside this new class:
public FragmentTest() {
super(MainActivity.class);
}
override the setUp() method (be sure to have R.id.fragmentContainer in your Activity class) where you will call at the end waitForIdleSync():
#Override
protected void setUp() throws Exception {
super.setUp();
// Starts the activity under test using
// the default Intent with:
// action = {#link Intent#ACTION_MAIN}
// flags = {#link Intent#FLAG_ACTIVITY_NEW_TASK}
// All other fields are null or empty.
testingActivity = getActivity();
testFragment = new TestFragment();
testingActivity.getFragmentManager().beginTransaction().add(R.id.fragmentContainer,testFragment,null).commit();
/**
* Synchronously wait for the application to be idle. Can not be called
* from the main application thread -- use {#link #start} to execute
* instrumentation in its own thread.
*
* Without waitForIdleSync(); our test would have nulls in fragment references.
*/
getInstrumentation().waitForIdleSync();
}
Write a test method, for example somethng like:
public void testGameFragmentsTextViews() {
String empty = "";
TextView textView = (TextView)testFragment.getView().findViewById(R.id.myTextView);
assertTrue("Empty stuff",(textView.getText().equals(empty)));
}
Run the test.
AndroidX provides a library, FragmentScenario, to create fragments and change their state.
app/build.gradle
dependencies {
def fragment_version = "1.0.0"
// ...
debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'
}
example
#RunWith(AndroidJUnit4::class)
class MyTestSuite {
#Test fun testEventFragment() {
// The "fragmentArgs" and "factory" arguments are optional.
val fragmentArgs = Bundle().apply {
putInt("selectedListItem", 0)
}
val factory = MyFragmentFactory()
val scenario = launchFragmentInContainer<MyFragment>(
fragmentArgs, factory)
onView(withId(R.id.text)).check(matches(withText("Hello World!")))
}
}
More at official docs.
Use your main activity as the test activity that you send to ActivityInstrumentationTestCase2. Then you can work with the fragments through the fragment manager of your main activity that launches the fragments. This is even better than having a test activity because it uses the logic that you write in your main activity to test scenarios, which gives a fuller and more complete test.
Example:
public class YourFragmentTest extends ActivityInstrumentationTestCase2<MainActivity> {
public YourFragmentTest(){
super(MainActivity.class);
}
}
Right now ActivityInstrumentationTestCase2 is deprecated. Now you can use rules in order to use activities within your tests: http://wiebe-elsinga.com/blog/whats-new-in-android-testing/
In order for those to work you'll have to add the dependencies to you build.gradle:
testCompile 'com.android.support.test:rules:0.5'
testCompile 'com.android.support.test:runner:0.5'
(See Why cannot I import AndroidJUnit4 and ActivityTestRule into my unit test class?)

Categories

Resources