0

how does stubbing works exactly ? If I understood correctly, a stub is an overwrite of a function of a mock. So when stubbing, mockito should only look at the signature of the function, not try to execute the function that will be stubbed.

But my test file fails when stubbing, the reason : it tries reach a null pointer inside the mocked function that is called in the original code of the to-be-stubbed function

Is it me that understands stubbing incorrectly or is Mockito not working as intended ?

Thank you in advance for your help.

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context 
android.content.Context.getApplicationContext()' on a null object reference
at com.micheladrien.fresquerappel.tools.JsonReader.loadJSONFromAsset(JsonReader.kt:50)
at com.micheladrien.fresquerappel.tools.JsonReader.readJsonObject(JsonReader.kt:17)
at com.micheladrien.android.fresquerappel.MainDataManagerTest.set_up(MainDataManagerTest.kt:24)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.mockito.internal.runners.DefaultInternalRunner$1$1.evaluate(DefaultInternalRunner.java:54)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.mockito.internal.runners.DefaultInternalRunner$1.run(DefaultInternalRunner.java:99)
at org.mockito.internal.runners.DefaultInternalRunner.run(DefaultInternalRunner.java:105)
at org.mockito.internal.runners.StrictRunner.run(StrictRunner.java:40)
at org.mockito.junit.MockitoJUnitRunner.run(MockitoJUnitRunner.java:163)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:395)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2081)

Here is my test file:

    package com.micheladrien.android.fresquerappel

    import com.micheladrien.fresquerappel.datas.RelationModel
    import com.micheladrien.fresquerappel.manager.MainDataManager
    import com.micheladrien.fresquerappel.tools.JsonReader
    import junit.framework.Assert.assertFalse
    import org.junit.Before
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.mockito.Mock
    import org.mockito.Mockito.*
    import org.mockito.junit.MockitoJUnitRunner

    @RunWith(MockitoJUnitRunner::class)
    class MainDataManagerTest {


    @Mock
    val mockJsonReader: JsonReader = mock(JsonReader::class.java)

    @Before
    fun set_up() {
        //Line that doesn't work
        `when`(mockJsonReader.readJsonObject("test")).thenReturn(mutableListOf<RelationModel>())
    }

    //Nous verifions qu'à la base, les données ne sont pas marquées comme chargées.
    @Test
    fun testDataUnloaded(){
        val singletonDataManager:MainDataManager.SingletonDataManager = MainDataManager.SingletonDataManager(mockJsonReader)
        //Assert/AssertFalse : verifie que la valeur est vrai ou fausse.
        assertFalse(singletonDataManager.isDataInitialised())
    }
   }

More:

    apply plugin: 'com.android.application'
    apply plugin: 'kotlin-android'
    apply plugin: 'kotlin-android-extensions'

    android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.micheladrien.fresquerappel"
        minSdkVersion 22
        targetSdkVersion 29
        versionCode 12
        versionName "1.2"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    }

    dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.3'
    def navigation_version = '2.3.1'
    implementation "androidx.navigation:navigation-fragment-ktx:$navigation_version"
    implementation "androidx.navigation:navigation-ui-ktx:$navigation_version"
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'
    implementation "androidx.navigation:navigation-fragment:$navigation_version"
    implementation "androidx.navigation:navigation-ui:$navigation_version"
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.annotation:annotation:1.1.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    implementation 'il.co.theblitz:observablecollections:1.4.2'
    def espressocore_version = '3.3.0'
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressocore_version"
    androidTestImplementation "androidx.test.espresso:espresso-core:$espressocore_version"
    androidTestImplementation "android.arch.core:core-testing:$lifecycle_version"
    def mockito_version = '3.5.5' // For local unit tests on your development machine
    testImplementation "org.mockito:mockito-core:$mockito_version" // For instrumentation tests on Android devices and emulators
    androidTestImplementation "org.mockito:mockito-android:$mockito_version"
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test:rules:1.0.2'
    androidTestImplementation 'com.android.support:support-annotations:24.0.0'
}

Edit : Here is JsonReader : package com.micheladrien.fresquerappel.tools

import android.content.Context
import android.util.Log
import com.micheladrien.fresquerappel.datas.RelationModel
import org.json.JSONArray
import org.json.JSONException
import java.io.IOException
import java.io.InputStream


open class JsonReader(val context:Context) {

fun readJsonObject(file_name:String):MutableList<RelationModel>{

    try {
        val jArray = JSONArray(loadJSONFromAsset(file_name))
        //val jArray = JSONArray("[{\"c1\": 1,\"c2\": 2,\"rel\": \"UP\",\"mandatory\": \"mandatory\",\"expl\": \"\"}]")
        val list = mutableListOf<RelationModel>()

        for (i in 0 until jArray.length()) {

            val card1: String =
                jArray.getJSONObject(i).getString("c1")
            val card2: String =
                jArray.getJSONObject(i).getString("c2")
            val direction: String =
                jArray.getJSONObject(i).getString("rel")
            val mandatory:String =
                jArray.getJSONObject(i).getString("mandatory")
            val explanation: String =
                jArray.getJSONObject(i).getString("expl")

            val rel = RelationModel(card1, card2, direction, mandatory, explanation)

            list.add(rel)
        }

        return list
    } catch (e: JSONException) {
        e.printStackTrace()
        return mutableListOf<RelationModel>()
    }
}

private fun loadJSONFromAsset(file_name:String): String? {
    val low_file_name : String = file_name.toLowerCase()

    val json = try {
        val assetManager = context.applicationContext.assets
        val `is`: InputStream = assetManager.open("json/" + low_file_name + ".json")
        val size: Int = `is`.available()
        val buffer = ByteArray(size)
        `is`.read(buffer)
        `is`.close()
        //Nous renvoyons le string pour json
        String(buffer, Charsets.UTF_8)
    }catch (ex: IOException) {
        ex.printStackTrace()
        return null
    }
    return json

}


}

The function that fails loadJSONFromAsset is private (it is the function that is called by the function that should be mocked). I put it public, but same error.

//Singleton : https://blog.mindorks.com/how-to-create-a-singleton-class-in-kotlin
package com.micheladrien.fresquerappel.manager

import android.content.Context
import android.util.Log
import com.micheladrien.fresquerappel.R
import com.micheladrien.fresquerappel.datas.Relation
import com.micheladrien.fresquerappel.datas.RelationDirection
import com.micheladrien.fresquerappel.datas.RelationMandatory
import com.micheladrien.fresquerappel.datas.RelationModel
import com.micheladrien.fresquerappel.fragment.single.Single
import com.micheladrien.fresquerappel.tools.JsonReader

class MainDataManager(context: Context) : DataManager {
val mainDataManager : SingletonDataManager = SingletonDataManager.getInstance(context)

init{
   if (!isDataInitialised()){
       loadData(context.getString(R.string.collage_climat))
   }

}

override fun isDataInitialised(): Boolean {
    return mainDataManager.isDataInitialised()
}

override fun loadData(file_name: String) {
    synchronized(mainDataManager){
        mainDataManager.loadData(file_name)
    }

}

override fun researchRelation(number1: Int, number2: Int): RelationModel {
    synchronized(mainDataManager) {
        return mainDataManager.researchRelation(number1, number2)
    }
}

override fun researchSingle(number1: Int): Single {
    synchronized(mainDataManager){
        return mainDataManager.researchSingle(number1)}
}



//Subclass. Cette classe ne sera instanciée qu'une fois
class SingletonDataManager(val jsonReader: JsonReader){

    companion object {
        lateinit private var instance : SingletonDataManager
        var boolInit:Boolean = false

        private fun isInitialised():Boolean{
            return boolInit
        }

        fun getInstance(context: Context): SingletonDataManager{
            if(isInitialised()) {
                return instance
            }
            else {
                val newJsonReader = JsonReader(context)
                instance = SingletonDataManager(newJsonReader)
                boolInit = true
                return instance
            }
        }
    }

    private var list: MutableList<RelationModel>? = null
    private var is_list_init:Boolean = false


    fun loadData(file_name : String){
        list = jsonReader.readJsonObject(file_name)
        Log.d("ami", "Nous avons init la bdd")
        is_list_init = true
    }

    fun isDataInitialised():Boolean{
        return this.is_list_init
    }

    fun researchRelation(number1:Int, number2:Int): RelationModel {
        list?.forEach {
            if(it.number1 == number1){
                if(it.number2 == number2)
                    return it
            }
        }
        return RelationModel(number1, number2,     Relation(RelationDirection.NONE, RelationMandatory.OPTIONAL) , "")
    }

    fun researchSingle(number1: Int): Single {
        TODO("Not yet implemented")
    }
}
}
AdrienM
  • 35
  • 5
  • Could you please post the code for `SingletonDataManager` and `JsonReader`? I think these would be very helpful in checking what's going on. One thing I know for example is that mockito won't mock final methods just like that, so if `loadJSONFromAsset` is `final` then I think you get into this behaviour you describe. – Fred Nov 11 '20 at 11:03
  • oh interesting, thank you. I added the two classes and some notes – AdrienM Nov 11 '20 at 13:18
  • Like I mentioned before, I think you get this behaviour if the method is final which is the case for `loadJSONFromAsset` - by default things are final in kotlin. Try adding the `open` keyword to the method – Fred Nov 11 '20 at 19:32
  • Of course, I forgot about that. Thank you – AdrienM Nov 12 '20 at 08:31

1 Answers1

0

As Fred said in the comment above :

you get this behaviour if the method is final which is the case for loadJSONFromAsset - by default things are final in kotlin. Try adding the open keyword to the method

This fixed the issue.

AdrienM
  • 35
  • 5