Сбой Mockito при отключении: он пытается выполнить функцию, которая должна быть отключена

#android #kotlin #mockito

#Android #kotlin #mockito

Вопрос:

как именно работает отключение? Если я правильно понял, заглушка — это перезапись функции макета. Поэтому при отключении mockito должен смотреть только на сигнатуру функции, а не пытаться выполнить функцию, которая будет отключена.

Но мой тестовый файл терпит неудачу при отключении, причина: он пытается достичь нулевого указателя внутри издевающейся функции, которая вызывается в исходном коде функции, подлежащей удалению

Это я неправильно понимаю отключение или Mockito работает не так, как предполагалось?

Заранее благодарим вас за помощь.

 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)
  

Вот мой тестовый файл:

     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())
    }
   }
  

Еще:

     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

}


}
  

Функция, которая завершается с ошибкой loadJSONFromAsset, является частной (это функция, вызываемая функцией, которую следует имитировать). Я разместил ее общедоступной, но с той же ошибкой.

 //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")
    }
}
}
  

Комментарии:

1. Не могли бы вы опубликовать код для SingletonDataManager and JsonReader ? Я думаю, это было бы очень полезно для проверки того, что происходит. Например, я знаю одну вещь: mockito не будет издеваться над конечными методами просто так, поэтому, если loadJSONFromAsset это final так, я думаю, вы попадаете в это поведение, которое вы описываете.

2. о, интересно, спасибо. Я добавил два класса и некоторые примечания

3. Как я упоминал ранее, я думаю, вы получаете это поведение, если метод является окончательным, что имеет место для loadJSONFromAsset — по умолчанию все является окончательным в kotlin. Попробуйте добавить open ключевое слово в метод

4. Конечно, я забыл об этом. Спасибо

Ответ №1:

Как сказал Фред в комментарии выше :

вы получаете это поведение, если метод является окончательным, что имеет место для loadJSONFromAsset — по умолчанию все является окончательным в kotlin. Попробуйте добавить ключевое слово open в метод

Это устранило проблему.