Reputation: 121
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
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)
}
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
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
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