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