Confused how to use Mockito for an android test - android

I'm trying to write a unit test for my android app but having trouble doing what I want with mockito. This is being used in conjunction with Robolectric which I have working just fine and have demonstrated that unit tests work.
I want to test whether or not a button will open a new activity depending on whether there is some bluetooth device connected. Obviously, there is no device connected with bluetooth in my test, however I want to pretend as though there is. The state of the bluetooth connection is stored in my Application class. There is no publicly accessible method to change this value.
So basically the logic in the app is like this:
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication();
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
So to test this I did the following:
TestHomeActivity.java:
#Test
public void buttonShouldOpenListIfConnected() {
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
//listViewButton was setup in #Before
listViewButton.performClick();
ShadowActivity shadowActivity = Robolectric.shadowOf(activity);
Intent intent = shadowActivity.getNextStartedActivity();
assertNotNull(intent); //this fails because no new activity was opened. I debugged this and found that isDeviceConnected returned false.
ShadowIntent shadowIntent = Robolectric.shadowOf(intent);
assertThat(shadowIntent.getComponent().getClassName(), equalTo(ListActivity.class.getName()));
}
So my unit test fails because the call (in the activity) to isDeviceConnected returns false even though I thought I told it to return true with the mock framework. I want my test to have this method return true though. Isn't this what mockito does or am I totally mistaken on how to use mockito?

That's how mockito works, but the problem is: is your listViewButton using your mockedApp? Seems not, because you're creating mockedApp at the test method and never setting it anywhere. Mockito will not mock the method calls of all instances of Application, only from what you declared as a mock.
I personally don't know how android works with the Application class, but you will have to set it somewhere so listView use your mockedApp instead of what it receives normally.
EDIT
After the updated question, you can transform your getApplication in a protected method, spy you listViewButton and make it return your mockedApp. That smells a little bad, but it's one way if you can not set your application mocked object to listViewButton.
EDIT2
Example of using spy in your test using BDDMockito for readability :)
public HomeActivity {
...
protected MyApplication getApplication() {
// real code
}
...
}
public void TestHomeActivity {
private HomeActivity homeActivity;
#Before
public void setUp() {
this.homeActivity = spy(new HomeActivity());
}
#Test
public void buttonShouldOpenListIfConnected() {
// given
FlexApplication mockedApp = Mockito.mock(MyApplication.class);
Mockito.when(mockedApp.isDeviceConnected()).thenReturn(true);
// IMPORTANT PART
given(homeActivity.getApplication()).willReturn(mockedApp);
...
}
}
After that, your test should work as expected. But I reinforce: Use spy only if you can't inject your mockedApp inside HomeActivity.

Your mocked version isn't being called.
See that call, getApplication()? (below). That's returning a real copy of your MyApplication class, not your mocked version. You'd need to intercept the getApplication() call and pass in your mocked Application object.
HomeActivity.java:
//this gets called when the button to open the list is clicked.
public void openListActivity(View button) {
MyApplication myApplication = (MyApplication) getApplication(); // returns the real thing
if (myApplication.isDeviceConnected() {
startActivity(new intent(this, ListActivity.class));
}
}
I'm not sure this is possible with Mockito. Have you tried customizing the ShadowActivity#getApplication() method?

Related

Patterns.EMAIL_ADDRESS returns null

#RunWith(MockitoJUnitRunner.Silent.class)
public class LoginActivityTest {
#InjectMocks
LoginActivity loginActivity;
private Pattern emailPattern;
#Before
public void createLogin(){
this.emailPattern = Patterns.EMAIL_ADDRESS;
}
#Test
public void checkValidation(){
mock(LoginActivity.class);
UserVO userVO = new UserVO();
userVO.setEmailID("invalid");
userVO.setPassword("a");
boolean b = loginActivity.validatesFields(userVO);
assertFalse(b);
}
}
this.emailPattern = Patterns.EMAIL_ADDRESS; This is creating null pointer object in MockitoJunitTestClass. But, when I run this on Activity it gets initialized properly.
Use PatternsCompat instead of Patterns
I was having a similar problem because it was just a simple test, but when I added #RunWith(AndroidJUnit4::class) the problem was fixed. Check if this is test that must run with Android resources or not.
I am a little confuse with your test:
You are mocking LoginActivity.class but not setting anything with that. I believe you want to do something like loginActivity = mock(LoginActivity.class); instead.
Also, your are mocking instead spying the class, so it won't access the real method in order to verify the flow of this method. In other words, your test is doing nothing in fact.
Finally, this emailPattern is never used on your test (probably it is used on you code), so I believe you want to mock it (I am supposing it). What I recommend you do is something like this:
#RunWith(MockitoJUnitRunner.Silent.class)
public class LoginActivityTest {
#Spy
#InjectMocks
private LoginActivity loginActivity;
#Mock
private OtherStuff otherStuff;
#Test
public void checkValidation(){
UserVO userVO = new UserVO();
userVO.setEmailID("invalid");
userVO.setPassword("a");
doReturn(Patterns.EMAIL_ADDRESS).when(otherStuff).doStuff();
boolean result = loginActivity.validatesFields(userVO);
assertFalse(result);
}
}
What I did here is just an example of unit test which is validating what validateFields() is doing. I suppose that inside this method you have some method on, what I name otherStuff, which calls a method that returns Patterns.EMAIL_ADDRESS, which is what you want to mock.
It would be really better if you insert the LoginActivity code to be more precise here, but I hope I helped you.

Using mockito to stub a void method

mockito-core:2.7.10
I am testing the following method using mockito
#Override
public void detachView() {
mMovieListViewContract = null;
mMovieModelContract.releaseResources();
}
I am stubbing the releaseResources method which has a void return. And testing to ensure the stubbed version gets called only once.
#Override
public void releaseResources() {
if(mSubscription != null && !mSubscription.isUnsubscribed()) {
mSubscription.unsubscribe();
}
}
I have written the following test:
#Test
public void shouldReleaseModelResourcesWhenDetached() {
doNothing().when(mockMovieListModelContract).releaseResources();
movieListPresenterContract.detachView();
verify(mockMovieListModelContract, times(1)).releaseResources();
}
I want to verify that the stubbed version of mockMovieListModelContract.releaseResources() gets called just once.
The following failed the test:
verify(mockMovieListModelContract, times(1)).releaseResources();
The test failed with:
Wanted but not invoked:
movieListModelContract.releaseResources();
Actually, there were zero interactions with this mock.
So I changed to using verifyZeroInteractions(mockMovieListModelContract); which passed the test. However, the test to verify that the stubbed version gets called just once.
Kind of straight forward: the mocking framework tells you that this method wasn't invoked on that mock object.
There are only two explanations for that:
You are not really running the production code you are showing in your question (you are testing something else)
Something is wrong with your setup; and you are not "inserting" the mocked object when doing the test setup

Cannot access MyApplication members from Mockito

This is my code:
#Mock
MyApplication application;
#Before
public void setup() {
application = Mockito.mock(MyApplication .class);
}
#Test
public void createProducts() {
application.ormLiteDatabaseHelper.getProductDao().create(new Product());
}
I get a NullPointerException at this line. My Application class inits the OrmLiteDatabaseHelper in its onCreate, how do I access the ormLiteDatabaseHelper from the mocked version of MyApplication?
application is a "mock" object. It only pretents to be a "MyApplication". Thus the only thing it can return for the method ormLiteDatabaseHelper is null and thus calling getProductDao on that will fail with a NullPointerException.
If you want it to return anything, then you need to tell your mock that, for example by...
OrmLiteDatabaseHelper ormLiteDatabaseHelper = ..something...;
Mockito.when( application.ormLiteDatabaseHelper ).thenReturn ( ormLiteDatabaseHelper);
Only then will your mock know to return something else than null. Of course, there are other ways, but for starters... Perhaps you also need partial mocking, which would be explained here, but without further info, it's hard to say.
Also, if you write #Mock, then you should either use the correct #RunWith annotation or call MockitoAnnotations.initMocks(this); instead of creating the mock manually via Mockito.mock. If you want to use the later, you don't need the #Mock annotation.

Android Espresso Intents test randomly fail with ``init() must be called prior to using this method``

I am working on pushing a project into espresso testing currently. I have read a bunch of documents and follow the given practises to get started.
Everything works fine, However, when it comes to Intents related test, the result is strange.
Most of the time, the tests passed in my Mac but fail in my colleague's Windows(not all tests fail) with the the fail message java.lang.IllegalStateException: init() must be called prior to using this method.
Quite strangely, If we Run Debug test in Android Studio flow the code step by step, it passes.
here is the test code:
#RunWith(AndroidJUnit4.class)
#LargeTest
public class MainActivityTest {
#Rule public IntentsTestRule<MainActivity> mRule = new IntentsTestRule<>(MainActivity.class, true, false);
AccountManager accountManager;
MainActivity activity;
private void buildLoginStatus() throws AuthenticatorException {
DanteApp app = (DanteApp) InstrumentationRegistry.getTargetContext().getApplicationContext();
accountManager = app.getDanteAppComponent().accountManager();
DoctorModel doctorModel = AccountMocker.mockDoctorModel();
accountManager.save(doctorModel.doctor);
accountManager.setAccessToken(doctorModel.access_token, false);
}
#Before public void before() throws Exception {
buildLoginStatus();
// must login
assertThat(accountManager.hasAuthenticated(), is(true));
activity = mRule.launchActivity(null);
// block all of the outer intents
intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
}
#After public void tearDown() throws Exception {
accountManager.delete();
}
// failed
#Test public void testViewDisplay() throws Exception {
// check tabhost is displayed
onView(withClassName(equalTo(TabHost.class.getName()))).check(matches(isDisplayed()));
// check toolbar is displayed
onView(withClassName(equalTo(ToolBar.class.getName()))).check(matches(isDisplayed()));
}
// passed
#Test public void testCallServiceHotline() throws Exception {
// switch to the account tab layout
onView(withChild(withText(R.string.account))).perform(click());
// click account menu to make a service call
onView(withId(R.id.contact)).perform(click());
// check call start expectly
intended(allOf(
not(isInternal()),
hasAction(Intent.ACTION_DIAL),
hasData(Uri.parse("tel:" + activity.getString(R.string.call_service)))
));
}
// failed
#Test public void testOpenSettingsUI() throws Exception {
// stub all internal intents
Intents.intending(isInternal())
.respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null));
onView(withChild(withText(R.string.account))).perform(click());
onView(withId(R.id.setting)).perform(click());
// check open settings activity successfully
intended(anyOf(
hasComponent(SettingActivity.class.getName())
));
}
}
The testing library version(nearly all dependencies are up to date and we use both physics devices and emulator to test):
rule: 0.4.1
runner: 0.4.1
espresso-*: 2.2.1
support-*: 23.1.0
Any idea deserves an appreciation. Thanks!
Two Solutions:
Use ActivityTestRule instead of IntentsTestRule and then in your #Before and #After manually call Intents.init() and Intents.release() respectively.
Write a custom IntentTestRule and override beforeActivityLaunched() to include your AccountManager logic. Use afterActivityFinished for your current #After logic. This will also allow you to just use the default IntentTestRule constructor. (Preferred Solution)
As to why this is happening:
"Finally on an unrelated note, be careful when using the new IntentsTestRule. It does not initialize, Intents.init(), until after the activity is launched (afterActivityLaunched())." - Shameless plug to my own post (halfway down helpful visual)
I think you are running into a race condition where in your #Before method you are executing launchActivity() then espresso tries to execute intending(not(isInternal())).respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK, null)); before your activity is actually created, which means afterActivityLaunched() isn't called, which means neither is Intents.init(), crash!
Hope this helps.
IntentsTestRule is derived from ActivityTestRule and should manage Intents.init() and Intents.release() for you.
However, in my case the IntentsTestRule did not work properly. So I switch back to ActivityTestRule and call Intents.init() before and Intents.release() after the test which sent the Intent.
For more information please see this reference.

Robolectric : findViewById gives view, but performClick() returns false

I'm using Robolectric to perform unit test on an android app.
The problem is simple : i can get my Button using findViewById, but calling on it either performClick()method or Robolectric.clickOn() will return false.
However the button works perfectly, tested through the app or with Robotium unit tests...
here is the test code that doesn't pass :
#RunWith(RobolectricTestRunner.class)
public class RegisterActivityTest {
private MainActivity mainActivity;
private LoginActivity loginActivity;
#Before
public void setUp(){
mainActivity=new MainActivity();
mainActivity.onCreate(null);
mainActivity.setContentView(R.layout.main);
}
#Test
public void testButton() throws Exception {
Button buttonPayment=(Button)mainActivity.findViewById(R.id.btn_payment);
assertNotNull(buttonPayment); // will pass test ( ---> the view is a button and exists )
assertTrue(buttonPayment.performClick()); // raise AssertionError
assertTrue(Robolectric.clickOn(buttonPayment)); // raise AssertionError as well
}
}
Thanks everybody.
Paul
According to the documentation for performClick(), the method will return true if you assigned an OnClickListener to the button.
...Did you?
Typically you would call setContentView within your onCreate method. Thus, the call to setContentView in your setUp method is redundant and may be causing issues.

Categories

Resources