Search code examples
androidunit-testingmockitorobolectricactivityunittestcase

btn.performClick() Unable mock Object,but Activity call the function is able


I have try test button function using Mockito. The code include Layout/SampleActivity/UnitTest major Content:

layout file define:

android:text="testbtnmock"
android:id="@+id/btn_testbtnmock"
android:onClick="testBtnMock"

SampleActivity file define

public void testBtnMock(View view) {
    System.out.println("value:"+getInt());
}

public int getInt(){
    return 0;
}

UnitTest file define

@RunWith(RobolectricGradleTestRunner.class)
@Config(constants = BuildConfig.class)
public class SampleActivityTest {
  private SampleActivity sampleActivity;
  private Button testBtnMock;
  @Before
  public void setUp() {
    ShadowLog.stream = System.out;
  }
  @Test
  public void testBtnMock() {
    sampleActivity = Robolectric.setupActivity(SampleActivity.class);
    SampleActivity spySampleActivity = spy(sampleActivity);
    when(spySampleActivity.getInt()).thenReturn(100);
    //spySampleActivity.testBtnMock(mock(View.class));  //it is working , print 100
    testBtnMock = (Button) sampleActivity.findViewById(R.id.btn_testbtnmock);
    testBtnMock.performClick(); //it is not work , print 0 ,mock invalid
  }
}

My questions are:

  1. why using spySampleActivity.testBtnMock(mock(View.class)); to trigger the function of testBtnMock(View v). It is working and prints 100.
  2. but using testBtnMock.performClick(); doesn't work and prints 0. Why? How to fix it?

Solution

  • That is quite easy to explain.

    The XML attribute will be used for calling the method via reflection (How does the android Xml attribute android:onClick="..." work behind the scenes?).

    So, with some simplification, the context from button will be used and method will be invoked by reflection. Button gets context during inflation and it is the reference to not spy activity. That is why real method called in your test.

    When you call the method through the reference to spy activity it works.

    How to fix:

    1. Extract a class that is responsible for providing integer
    2. Mock it and inject in test

    Something like:

    public class IntegerProvider {
      public int getInt(){
         return 0;
      }  
    }
    
    public class SampleActivity {
       IntegerProvider intProvider;
    
       public void testBtnMock(View view) {
         System.out.println("value:" + intProvider.getInt());
       }
    }
    
    @Test
    public void testBtnMock() {
        sampleActivity = Robolectric.setupActivity(SampleActivity.class);
        IntegerProvider providerMock = mock(IntegerProvider.class);
        when(providerMock.getInt()).thenReturn(100);
        sampleActivity.intProvider = providerMock;
    
        testBtnMock = (Button) sampleActivity.findViewById(R.id.btn_testbtnmock);
        testBtnMock.performClick();
    }