Hot questions for Using Mockito in automated tests

Top 10 Java Open Source / Mockito / automated tests

Question:

I want to be able to make unit tests and instrumentation tests in Android Studio, and using Mockito in them.

I'm using the new approach for tests in Android Studio 0.8. This is:

  • building with gradle
  • using official Android API for testing (ActivityInstrumentationTestCase2, etc)
  • having the tests inside the directory of the app, not as a separate module
  • launching the tests in Android Studio as a "Android Test" run configuration

How can I write code in my tests that depends on libraries used only for the tests, such as mockito or hamcrest?

I'd like to include these libraries when compiling and running the tests, but avoid them to be exported to the released .apk.

In https://code.google.com/p/mockito/wiki/DeclaringMockitoDependency I've read that I should add the dependency as:

dependencies {
    ....
    testCompile "org.mockito:mockito-core:1.9.5"
}

But when running I get:

Build script error, Unsupported Gradle DSL method found: 'testCompile()'!

Although I'm not sure it's relevant, the gradle build file I'm using is:

apply plugin: 'android'

dependencies {
    compile fileTree(dir: 'libs', include: '*.jar')
    compile project(':android-sdk')
    testCompile "org.mockito:mockito-core:1.9.5"
}

android {
    compileSdkVersion 19
    buildToolsVersion "20.0.0"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        androidTest.setRoot('tests')

        // Note - to run the tests from command line:
        // $ gradle clean connectedCheck build
        // (requires gradle 1.10)

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}

Answer:

I got it working by using the "androidTestCompile" options under "dependencies", as explained here.

What I have done is:

  • created a folder called libs-tests with the jars that should only be used for testing.
  • added that folder as a dependency for tests with "androidTestCompile"

Now, the gradle build file stands as:

apply plugin: 'android'

dependencies {
    compile project(':android-sdk')

    // The libs folder is included in the apk of the real app
    compile fileTree(dir: 'libs', include: '*.jar')

    // The tests-libs folder is included only for tests
    androidTestCompile fileTree(dir: 'libs-tests', include: '*.jar')
}


android {
    compileSdkVersion 19
    buildToolsVersion "20.0.0"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }


    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }

        // Move the tests to tests/java, tests/res, etc...
        androidTest.setRoot('tests')

        // Note - to run the tests from command line:
        // $ gradle clean connectedCheck build
        // (requires gradle 1.10)

        // Move the build types to build-types/<type>
        // For instance, build-types/debug/java, build-types/debug/AndroidManifest.xml, ...
        // This moves them out of them default location under src/<type>/... which would
        // conflict with src/ being used by the main source set.
        // Adding new build types or product flavors should be accompanied
        // by a similar customization.
        debug.setRoot('build-types/debug')
        release.setRoot('build-types/release')
    }
}

Question:

I have a problem with instrumental test that is checking activity that in one of methods saves its state to shared preferences. Tested code looks like that:

initialPresenter.getLocalData().edit()
                    .putString("SessionDetails", new Gson().toJson(sessionData))
                    .putBoolean("relog", false)
                    .apply();

LocalData is injected into presenter by dagger2. I've created mocks for it and I'm repleacing them so everything works fine there; eg.

when(localData.getBoolean("signed_using_email", false)).thenReturn(true);

Problem occurs when I'm trying to somehow disable or ommit editing data. I've created another mock; this time of editor so when SharedPref calls edit it gets explicit mock;

@Mock SharedPreferences.Editor mEditor;
.
.
.
when(localData.edit()).thenReturn(mEditor);

but then I get error:

Caused by: java.lang.NullPointerException: Attempt to invoke interface method 'android.content.SharedPreferences$Editor android.content.SharedPreferences$Editor.putBoolean(java.lang.String, boolean)' on a null object reference

Which btw. is freaking weird, why on putBoolean no putString? It seems like first mock works just fine, but then it gets nested (?) and throws error.

Also tried another approach, instead of stubbing/replacing answer I've used doNothing;

doNothing().when(localData).edit();

But it also caused similar problem throwing error:

org.mockito.exceptions.base.MockitoException:
Only void methods can doNothing()!
Example of correct use of doNothing():
doNothing().
doThrow(new RuntimeException())
.when(mock).someVoidMethod();
Above means:
someVoidMethod() does nothing the 1st time but throws an exception the 2nd time is called

Any ideas how to fix it? I don't need to save any state, I can mock it later, which is fine because I'll get documentation then by writing these tests. Earlier I was using PowerMockito to suppress whole method that uses sharedPreferences but this solution doesn't seem to be to good.


Answer:

The problem here is that SharedPreferences.Editor has a 'builder' syntax where each call putString(), putBoolean() etc. return the Editor.

When you mock this object, you want to mimic this behaviour by having the mock return itself each time one of those methods is invoked.

As per Jeff Bowman's answer on mocking builder syntax with Mockito you can do this with the following change in your code:

@Mock(answer = RETURNS_SELF) SharedPreferences.Editor mEditor;

Alternatively, you may just want to use RETURNS_DEEP_STUBS:

mEditor = mock(SharedPreferences.Editor.class, RETURNS_DEEP_STUBS);

Question:

How to use with Mocking (Mockito) with Robolectric can any one suggest me and i want to write the unit test cases for an API call also, i try with Robolectric but it only runs the label and actions but for API calls how to write the unit test in Android Test package,Please help me out


Answer:

you don't combine Mockito with Robolectric, as far as I understand testing on Android.

You use Robolectric in order to not having to test your instrumented tests on emulator or physical device, instead, you test it on JVM. That is Robolectric. This allows you also to get access to android framework, and is slower.

Mockito, on the other hand, is a library that allows you to :mock: your dependencies inside a unit test. Unit test's purpose is to test the behaviour of the specific software entity, concretely its logic, that you wish to test. Since it don't have access to android framework (it don't need that), it is quite faster.

Edit:

It's actually more complicated. You can have Robolectric together with Mockito (by introducing Mockito rule as a Runner - you have 2 runners then, one @RunWith(AndroidJunit4) - robolectric one, with MockitoRule). And you can just unit test something that needs access to android SDK with Robolectric without actually starting an activity... So these are test configurations I can come up with on Android:

  1. (/test) Simple JUnit

  2. (/test) JUnit + Mockito - classic unit testing in design patterns

  3. (/test) AndroidJUnit (Robolectric) + Mockito without starting activity - unit testing that requires both android sdk instances and mocking

  4. (/test) **AndroidJUnit (Robolectric) - starts activity, you test UI with espresso

    1. (/instrumentedTest) AndroidJUnit - the same thing, test ui with espresso

Or you can also have sharedTest. Just one folder for both. With this you can maybe achieve an option to run one test both on device and on VM, I'm just not sure how. Maybe Nitrogen will introduce this option in the future. https://blog.danlew.net/2015/11/02/sharing-code-between-unit-tests-and-instrumentation-tests-on-android/

The ways you can set up your test environment for particular class are many nowadays. I hope that project Nitrogen will simplify this decision making and provide a good documentation on this.

Edit

Actually I was wrong when I wrote this comment. You can mock dependencies inside robolectric test. You do that through dependency injection (Koin, Dagger 2) and creating different dependency graph with the same abstractions. (Complicated, check that in some blogs... won't elaborate it here more)

Question:

I want to stop the execution of a test if it matches a certain scenario in order to avoid code duplication.

Consider the following situation:

CoreProviderTest

  public void executeCoreSuccess(Object responseModel){
     assertNotNull("Response successful", responseModel != null);

     if (responseModel == null) {
        //Kill Test
     }
  }

ChildProviderTest - extends CoreProviderTest

 @Test
public void responseTester() {

    new Provider().getServiceResponse(new Provider.Interface() {
        @Override
        public void onSuccess(Object responseModel) {
            executeCoreSuccess(responseModel);
            //Continue assertions
        }

        @Override
        public void onFailure(ErrorResponseModel error) {
            executeCoreFailure(error);
        }
    });
}

For a null response, I would like to kill my current test case inside CoreProviderTest otherwise that might trigger exceptions in further assertions. I wanted to avoid something like:

CoreProviderTest

if (responseModel == null) {
    return true;
}

ChildProviderTest

@Override
public void onSuccess(Object responseModel) {
   if (executeCoreSuccess(responseModel))
       return;

     //Continue assertions
}

Is there a way to kill the current test execution with Mockito, JUnit or Roboletric? No luck so far googling an answer.

Thanks in advance


Answer:

If you are using JUnit5, it has features like Assumtions, Disabling tests and Conditional Test Execution. Here's the link :

https://junit.org/junit5/docs/current/user-guide/#writing-tests-assumptions

In your case, looks like assumingThat should work. Here's the API : https://junit.org/junit5/docs/5.0.0/api/org/junit/jupiter/api/Assumptions.html#assumingThat-boolean-org.junit.jupiter.api.function.Executable-

Question:

I am looking for any automated tool which can give me a summary of which Presenter classes in my MVP doesn't have a Test classes along with it.

Like I wrote some business logic in my Presenter class but I forgot to write test cases for this class, any automated tool to point this out?


Answer:

JaCoCo is a great tool to generate tests coverage reports. Anyway, Android plugin only generates the coverage report from instrumented tests. If you want to include the unit testing, it is necessary to create the task manually.

In the task, it is possible to exclude view classes from the report, for example:

def fileFilter = [
    'com/sample/**/view/**.*',
    '**/R.class', 
    ...]

Usually, I exclude Android classes (BuildConfig, R, etc) and any other XML file that is out of my test strategy.

You can find more information here: https://docs.gradle.org/current/userguide/jacoco_plugin.html

Hope to be helpful, good luck.

Question:

I have a spring-boot app with this architecture:

@Controller > @Service > @Component

Here's my Component:

@Component
public class myComponent {

    @Autowired
    private ObjectMapper mapper;
    //com.fasterxml.jackson.databind.ObjectMapper

    @Autowired
    private Component2 component2;
    // some 3rd part REST service

    @Autowired
    private Component3 component3;
    //database access

    @Transactional(propagation = Propagation.REQUIRED)
    public List<SomePOJO> method1(String otherString) {
        String newOne = myString + otherString; //more logic here, but without components
        return this.method2(newOne);
    }

    public List<SomePojo> method2(String newOne){
        String one = component3.methodX(newOne); //access database with component3
        return component2.methodY(one); //more logic here, including component2 and mapper!
    }

}

Using Mockito I implement this at my test class:

@MockBean
public myComponent component;

@Before
public void before() throws Exception {
    MockitoAnnotations.initMocks(this);
    List<POJO> someList = new ArrayList<>(); //list populated with some specific POJO's
    Mockito.when(component.method1("specificString")).
        thenReturn(someList);
}

@Test
public void recuperaDados() throws Exception  {
    String response = given().authentication().preemptive().oauth2("loginInfo")
            .get("myComponent_method1_path/" + "specificString1").asString();
    //this specificString1 leads to myComponent.method1("specificString"), satisfying Mockito 
}

And with this I successfully accomplished my tests. But when I use

Mockito.when(component.method2("specificString2")).thenReturn(someList);

And then

String response1 = given().authentication().preemptive().oauth2("loginInfo")
            .get("myComponent_method1_path/" + "specificString1").asString();
// this specificString1 leads to myComponent.method2("specificString2"), also satisfying Mockito
String response2 = component.method2("specificString2");
String response3 = component.method2("randomString");

response1 is "", response2 is someList correctly stringfied and response3 is null. response2 and response3 are expected. But I expected response1 to be same as response2 or at least null. How can I correctly mock a method called by other inside same class and test calling the other?

EDIT

My test class extends this class:

@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)
public abstract class AbstractRestTest {

    @Autowired
    private WebApplicationContext wac;

    protected Dsl dsl = Dsl.getInstance();

    @Before
    public void globalSetup() {
        RestAssuredMockMvc.webAppContextSetup(wac);
    }

}

Answer:

Mockito works by proxying the object that you wish to stub, and as such your implementation code will never be executed when calling methods on a mocked object unless specifically defined to do so. Consequently by calling method1 on the mock object, method2 will never be called as a result.

There is however a way to achieve your desired affect, by partial mocking. In your test in which you wish to test method2, you can indicate that the real method1 should be executed:

when(component.method1("specificString")).thenCallRealMethod();

It should be noted that the mockito documentation indicates that using partial mocks may be indicative of a code smell. In this cause the question is, does method2 really have to be public? If it is only public because you wished to mock it's implementation this is a mistake. With mocks you only wish to stub the public api of an object. Any private or helpers methods should be considered internal and ignored. The same goes for if you where testing that object, only the public methods should be tested, with helper methods tested indirectly.

You are right that the first result being an empty string instead of null is odd. I assume that it may have to do with something in code that has not be provided, e.g the controller or service.