#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
andJsonReader
? Я думаю, это было бы очень полезно для проверки того, что происходит. Например, я знаю одну вещь: mockito не будет издеваться над конечными методами просто так, поэтому, еслиloadJSONFromAsset
этоfinal
так, я думаю, вы попадаете в это поведение, которое вы описываете.2. о, интересно, спасибо. Я добавил два класса и некоторые примечания
3. Как я упоминал ранее, я думаю, вы получаете это поведение, если метод является окончательным, что имеет место для
loadJSONFromAsset
— по умолчанию все является окончательным в kotlin. Попробуйте добавитьopen
ключевое слово в метод4. Конечно, я забыл об этом. Спасибо
Ответ №1:
Как сказал Фред в комментарии выше :
вы получаете это поведение, если метод является окончательным, что имеет место для loadJSONFromAsset — по умолчанию все является окончательным в kotlin. Попробуйте добавить ключевое слово open в метод
Это устранило проблему.