I'm using firebase in my project and i'm testing all the functionalities which includes firebase components and i recently tried to test firebase database but it is throwing an error which i could not understand if someone could help , i would appreciate it , Thank you

  • error that i'm getting
  Attempt to invoke virtual method 'com.google.firebase.database.DatabaseReference com.google.firebase.database.DatabaseReference.child(java.lang.String)' on a null object reference
    at com.revert.journey.app.chatui.ChatHomeActivityTest.testCaseSendMessage
  • This is my testing code
  @Test
    fun testCaseSendMessage(){
        val databaseMock = mock(DatabaseReference::class.java)
        ServiceLocator.reference = databaseMock
        `when`(databaseMock.child("Messages").child("1251515").setValue(Utils.message()))
            .thenReturn(isNotNull())

    }
  • This is my real code
    val messageMap = hashMapOf<String,Any>()
        messageMap["userName"] = userName
        messageMap["userMessage"] = message
        messageMap["userPic"] = userPic
        messageMap["messageTiming"] = Calendar.getInstance().timeInMillis.toString()
        messageMap["chatImage"] = downloadUrl
        messageMap["uid"] = firebaseAuth.currentUser!!.uid

        ServiceLocator.reference
            .child("Messages")
            .child(System.currentTimeMillis().toString())
            .setValue(messageMap).await()
  • ServiceLocator class
object ServiceLocator {

    var firebaseAuth = FirebaseAuth.getInstance()
    var reference = FirebaseDatabase.getInstance().reference

}
  • Image Sample from my firebase

enter image description here

  • UPDATE
 var base = ServiceLocator.reference.child("Messages")
 var child = base.child(System.currentTimeMillis().toString())
 child.setValue(messageMap).await()

Solution 1:

If you want to Mock the full call chain you could create mocks for the intermediate states like this (I don't know all the right types to use here for realtime database, but a similar approach works for Firestore)

val databaseMock = mock(DatabaseReference::class.java)
val childMock = mock(Reference::class.java)
val mockTask = mock(??) // set type to whatever "setValue" returns

doReturn(childMock).`when`(databaseMock).child(anyString())
doReturn(childMock).`when`(childMock).child(anyString())
doReturn(mockTask).`when`(childMock).setValue(any())

If you want to actually test that the correct value was set, you can add a listener to the mock to intercept the actual value passed to it

doAnswer { invocation ->
    val args = invocation.arguments
    val l = args[0] as Map<String,Any>
    //add tests here to assert that the map values you sent are correct
    null
}.`when`(childMock).setValue(any())

Debugging Tips

If you want to diagnose what is going on in a scenario like this you can change the chained call in your real code to something like the code below. Then if one of the calls returns null you will know exactly which one it is and can add the missing mock for it.

val db = ServiceLocator.reference
val cm = db.child("Messages")
val ct = cm.child(System.currentTimeMillis().toString())
val response = ct.setValue(messageMap)
response.await()

None of these call should access your database in the test, so the actual database schema does not matter. All that matters is getting the mocks set correctly (since you are using a mock database anyway)