Testing a fragment (Android), mocking getActivity() call - android

I want to test a Fragment with AndroidTest cases and Mockito (I am using mockito for other test cases).
I´ve done this before with my own code (coded in a different way) but in this case, I am testing a Fragment and I want to mock this call: final PackageManager packageManager = getActivity().getPackageManager();
I will put you here part of the TestClass, and part of the Fragment that I want to test.
Thanks in advance for your ideas or suggestions.
public class MyFragmentTest extends
ActivityInstrumentationTestCase2<MyActivity>{
MyFragment myFragment;
public MyFragmentTest () {
super(MyActivity.class);
}
#Override
public void setUp() throws Exception {
super.setUp();
// This have to be done because of some issues with dexmaker
System.setProperty("dexmaker.dexcache", "/sdcard");
// This have to be done because of the sharedUserId problem
Thread.currentThread().setContextClassLoader(
getClass().getClassLoader());
myFragment = new MyFragment() {
//I can override methods here
};
}
public void testMyMethod() throws Exception {
myFragment.methodThatIWantToTest();
}
}
/************ CLASS THAT I WANT TO TEST *********/
public class MyFragment extends Fragment{
public void methodThatIWantToTest(){
/*..... more lines */
final PackageManager packageManager = getActivity().getPackageManager();
/*..... more lines ...*/
}
}

I have employed this hack:
// Needed because Fragment.mActivity is package-private
package android.support.v4.app;
public class FragmentInjector {
public static void injectActivity(Fragment fragment, FragmentActivity fragmentActivity) {
fragment.mActivity = fragmentActivity;
}
}
Alternatively you could employ reflection to change the value of fragment.mActivity. I don't know of any other way.

Related

PowerMock argument captor returns null for new Intent

Here is my main class and the method that I'm trying to test
public class MyClass {
public void startEmailActivity(FragmentActivity activity, #NotNull String emailUrl) {
if (isMyEmailAppInstalled()) {
Intent myEmailAppIntent = new Intent(Intent.ACTION_SENDTO, Uri.parse(emailUrl));
myEmailAppIntent.setClassName(MY_PACKAGE_NAME, MY_EMAIL_COMPOSE_ACTIVITY_EMAIL);
activity.startActivity(intent);
}
}
And here is the test class and method. Assume that I've mocked necessary calls inside isMyEmailAppInstalled() method such that it returns true
#RunWith(PowerMockRunner.class)
#PrepareForTest(Uri.class)
public class MyClassTest {
#Mock
FragmentActivity mockActivity;
#Mock
private Uri mockUri;
#Captor
private ArgumentCaptor<Intent> intentArgumentCaptor;
private static final String MOCK_EMAIL_URL = "mailto:mock#mock.com";
#Test
public void testStartEmailActivity() throws Exception {
doNothing().when(mockActivity).startActivity(any(Intent.class));
mockStatic(Uri.class);
when(Uri.parse(MOCK_EMAIL_URL)).thenReturn(mockUri);
MyClass testObject = new MyClass();
testObject.startEmailActivity(mockActivity, MOCK_EMAIL_URL);
intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mockActivity).startActivity(intentArgumentCaptor.capture());
Intent sentIntent = intentArgumentCaptor.getValue();
//sentIntent is null here :( Below lines of code throws NPE when test is run
Assert.assertTrue(sentIntent.getComponent().getClassName().equalsIgnoreCase(MY_EMAIL_COMPOSE_ACTIVITY_EMAIL));
Assert.assertTrue(sentIntent.getComponent().getPackageName().equalsIgnoreCase(MY_PACKAGE_NAME));
}
}
Anyone have an idea why the argument captor would return null? It seems like simple thing, may be I'm missing something.
Aha! I found out the missing piece.
Basically I needed two things.
add the preparefortest for the class under test which will allow powermock to get inside this and help with mocking i.e
#PrepareForTest({Uri.class, MyClass.class})
mock the construction of new object using whenNew()
whenNew(Intent.class).withAnyArguments().thenReturn(mockIntent);
So this is what my final test method looks like
#Test
public void testStartEmailActivity() throws Exception {
doNothing().when(mockActivity).startActivity(any(Intent.class));
mockStatic(Uri.class);
when(Uri.parse(MOCK_EMAIL_URL)).thenReturn(mockUri);
when(mockIntent.setClassName(MY_PACKAGE_NAME, MY_EMAIL_COMPOSE_ACTIVITY_EMAIL)).thenReturn(mockIntent);
whenNew(Intent.class).withAnyArguments().thenReturn(mockIntent);
MyClass testObject = new MyClass();
testObject.startEmailActivity(mockActivity, MOCK_EMAIL_URL);
verify(mockActivity).startActivity(mockIntent);
}

ClassCastException onAttach() when unit testing Android Fragment

I'm trying to test a Fragment that has in interface that must be implemented by the hosting Activity and gets casted to that specific interface's type through onAttach().
Problem: I'm not sure how to implement the necessary interface methods within an Android Unit Test or if it's even necessary to do so. Surprisingly, I haven't found any posts or forums that address this issue.
Test:
public class FragmentTest {
private ActivityForUnitTesting fragmentHostActivity;
private ExampleFragment fragmentToTest;
#Rule
public ActivityTestRule activityTestRule = new ActivityTestRule<>(ActivityForUnitTesting.class);
#Before
public void setUp() {
fragmentHostActivity = (ActivityForUnitTesting) activityTestRule.getActivity();
fragmentManager = fragmentHostActivity.getSupportFragmentManager();
fragmentToTest = new ExampleFragment();
}
#Test
public void testExample() {
fragmentManager.beginTransaction()
.replace(R.id.frame_layout_container, fragmentToTest)
.commit();
}
}
Fragment:
public class ExampleFragment extends Fragment {
private ExampleFragmentListener exampleFragmentListener;
...
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
exampleFragmentListener = (ExampleFragmentListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement ExampleFragmentListener");
}
}
...
}
but when I try running a simple test I get:
java.lang.ClassCastException: com.example.package.ActivityForUnitTesting#1234567 must implement ExampleFragmentListener
at com.example.package.ExampleFragment.onAttach(ExampleFragment.java:)
I know that the issue is that my Unit Test ActivityForUnitTesting object does not implement the required interface methods. My question is, how do I safely implement those methods within my Unit Test. I haven't had any luck finding a similar question or a solid example.
I didn't find a solution to this, but I did find a "workaround". Instead of using onAttach(), explicitly set your listener through a public method.
public class ExampleFragment extends Fragment {
private ExampleFragmentListener exampleFragmentListener;
...
//#Override
//public void onAttach(Context context) {
// super.onAttach(context);
// try {
// exampleFragmentListener = (ExampleFragmentListener) context;
// } catch (ClassCastException e) {
// throw new ClassCastException(context.toString() + " must implement ExampleFragmentListener");
// }
//}
public void setExampleFragmentListener(ExampleFragmentListener exampleFragmentListener) {
this.exampleFragmentListener = exampleFragmentListener;
}
...
}
then, you should already have ExampleFragmentListener implemented in your host Activity. Just call
setExampleFragmentListener(ActivityOrClassThatImplementsExampleFragmentListener)
from wherever you perform your Activity setup. As a result, the test shouldn't complain about unimplemented methods.

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

Mocking findViewById response with Robolectric and Mockito

I have a custom ListView say CustomListView:
In a fragment there is:
<com.custom.CustomListView
android:id="#+id/custom_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
and in that fragment's source, I have
private CustomListView mCustomListView;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mContext = getActivity();
mCustomListView = mContext.findViewById(R.id.custom_listview);
}
Then there is some method later:
public void doSomethingOnReceivingData(Data data) {
mCustomListView.someCustomMethod(data);
}
I want to write test for doSomethingOnReceivingData(Data) method.
I can not figure out how to mock the listview so that I can continue with the test (ArgumentCaptors and stuff)?
I would give to list field package local access and mock it in test directly. For our app it is already package accessible since we use Butterknife
#RunWith(MockitoJUnitRunner.class)
public class MainActivityFragmentTest {
#Mock
private CustomListView mCustomListView;
#InjectMocks
private MainActivityFragment fragment;
#Test
public void doSomethingOnReceivingData_callsCustomListView() {
final String data = "data";
fragment.doSomethingOnReceivingData(data);
verify(mCustomListView).someCustomMethod(eq(data));
}
}

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