Ashish Jha
Ashish Jha

Reputation: 121

How to mock Kotlin's kotlinx.android.synthetic views in Android

I have a fragment written in Kotlin. I import layout views using

import kotlinx.android.synthetic.main.my_fragment_layout.

In one of my methods I am setting the TextView's text:

fun setViews() {
    myTextView.text = "Hello"
    // In Java I would have used:
    // (getView().findViewById(R.id.myTextView)).setText("Hello");
}

In my plain JVM unit test, I want to test this method using Mockito. For example, if the above method was written in java I could do:

public void setViewsTest() {
    // Mock dependencies
    View view = Mockito.mock(View.class);
    TextView myTextView = Mockito.mock(TextView.class);
    when(fragment.getView()).thenReturn(view);
    when(view.findViewById(R.id. myTextView)).thenReturn(myTextView);

    // Call method
    fragment.setViews();

   // Verify the test
   verify(myTextView).setText("Hello");
}

How can I do a similar implementation when using Kotlin's kotlinx.android.synthetic views?

Upvotes: 8

Views: 3361

Answers (3)

Redwid
Redwid

Reputation: 313

I've got the same issue.

Found that Kotlin extension adds some functionality to view/fragments. Let's say - I have Fragment - SampleFragment, where onViewCreated() I'm accessing some views after inflation:

    override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    with(webView) {
        webViewClient = createWebClient()
    }
}

Of course my unit test failed on webViewClient = createWebClient()

So, let's see what kotlin extension added to my fragment (go to Tools->Kotlin->Show Kotlin Byte Code and then select decompile).

public final class SampleFragment extends Fragment {
    ...
    private HashMap _$_findViewCache;

    public View _$_findCachedViewById(int var1) {
      if (this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(var1);
      if (var2 == null) {
         View var10000 = this.getView();
         if (var10000 == null) {
             return null;
         }

         var2 = var10000.findViewById(var1);
         this._$_findViewCache.put(var1, var2);
      }

      return var2;
   }

Now, we have HashMap that stores all our ids->Views key value pairs. There are possible two solutions: 1. Spy your object and return correct view:

@Mock lateinit var view: View
@Mock lateinit var webView: WebView

@Before
fun setUp() {
    sampleFragment = spy(SampleFragment())
    doReturn(view).`when`(sampleFragment).view
    doReturn(webView).`when`(view).findViewById<WebView>(R.id.webView)
}
  1. Use reflection (mockito Whitebox) to initialize _$_findViewCache HashMap.

    var hashMap = java.util.HashMap() hashMap.put(R.id.webView, webView) Whitebox.setInternalState(sampleFragment, "_\$_findViewCache", hashMap)

May be there is another more Kotlin specific way to avoid this issue?

Upvotes: 0

Sebastian
Sebastian

Reputation: 2956

I've been looking for a solution for this problem too, and this is what I'm currently doing to mock views to be able to verify them.

This solution does not use kotlinx.android.synthetic since I haven't yet found a way to avoid the "Method not mocked" exception, instead I go with the Butterknife way.

In the activity or fragment:

@BindView(R.id.my_text_view)
internal lateinit var myTextView: TextView
// ...
fun setViews() {
    myTextView.text = "Hello"
}

In the test:

import com.nhaarman.mockito_kotlin.mock

class MyActivityTest {

    private val underTest = MyActivity()

    @Before
    fun setUp() {
        underTest.myTextView = mock()
    }

    @Test
    fun setViews() {
        underTest.setViews()

        verify(underTest.myTextView).text = "Hello"
    }
}

Upvotes: 1

andrei_zaitcev
andrei_zaitcev

Reputation: 1458

I think that Robolectric is a more proper tool for such type of tests. Using it you can have an easier way to test a code with Android dependencies on JVM.

For example, you test will look like something like this:

@Test
fun `should set hello`() {
    val fragment = YourFragment()

    fragment.setViews()

    assertEquals(fragment.myTextView.getText().toString(), "Hello");
}

Upvotes: 2

Related Questions