Search code examples
mockitoscalatest

How come the test case is still passing even if I have not provided correct mocking in my opinion


I am testing this function. The main bit for me is the call to add method of a respository (partitionsOfATagTransactionRepository.add(transaction, infoToAdd,mutationCondition))

def updateOrCreateTagPartitionInfo(transaction:DistributedTransaction,currentTagPartition: Option[TagPartitions], tag: String) = {
    val currentCalendar = Calendar.getInstance() //TODOM - should I use a standard Locale/Timezone (eg GMT) to keep time consistent across all instances of the server application
    val currentYear = currentCalendar.get(Calendar.YEAR).toLong
    val currentMonth = currentCalendar.get(Calendar.MONTH).toLong
    val newTagParitionInfo = TagPartitionsInfo(currentYear.toLong, currentMonth.toLong)
    val (infoToAdd,mutationCondition) = currentTagPartition match {
      case Some(tagPartitionInfo) => {
        //checktest-should add new tag partition info to existing partition info
        (TagPartitions(tagPartitionInfo.tag, tagPartitionInfo.partitionInfo + (newTagParitionInfo)),new PutIfExists)
      }
      case None => {
        //checktest-should add new tag partition info if  existing partition doesn't exist
        (TagPartitions(tag, Set(newTagParitionInfo)),new PutIfNotExists)
      }
    }
    partitionsOfATagTransactionRepository.add(transaction, infoToAdd,mutationCondition) //calling a repositoru method which I suppose needs mocking
    infoToAdd
  }

I wrote this test case to test the method

"should add new tag partition info if  existing partition doesn't exist" in {
      val servicesTestEnv = new ServicesTestEnv(components = components)
      val questionTransactionDBService = new QuestionsTransactionDatabaseService(
        servicesTestEnv.mockAnswersTransactionRepository,
        servicesTestEnv.mockPartitionsOfATagTransactionRepository,
        servicesTestEnv.mockPracticeQuestionsTagsTransactionRepository,
        servicesTestEnv.mockPracticeQuestionsTransactionRepository,
        servicesTestEnv.mockSupportedTagsTransactionRepository,
        servicesTestEnv.mockUserProfileAndPortfolioTransactionRepository,
        servicesTestEnv.mockQuestionsCreatedByUserRepo,
        servicesTestEnv.mockTransactionService,
        servicesTestEnv.mockPartitionsOfATagRepository,
        servicesTestEnv.mockHelperMethods
      )

      val currentCalendar = Calendar.getInstance() //TODOM - should I use a standard Locale/Timezone (eg GMT) to keep time consistent across all instances of the server application
      val currentYear = currentCalendar.get(Calendar.YEAR).toLong
      val currentMonth = currentCalendar.get(Calendar.MONTH).toLong
      val newTagParitionInfo = TagPartitionsInfo(currentYear.toLong, currentMonth.toLong)

      val existingTag = "someExistingTag"
      val existingTagPartitions = None
      val result = questionTransactionDBService.updateOrCreateTagPartitionInfo(servicesTestEnv.mockDistributedTransaction,
        existingTagPartitions,existingTag) //calling the funtion under test but have not provided mock for the repository's add method. The test passes! how? Shouldn't the test throw Null Pointer exception?
      val expectedResult = TagPartitions(existingTag,Set(newTagParitionInfo))
      verify(servicesTestEnv.mockPartitionsOfATagTransactionRepository,times(1))
        .add(servicesTestEnv.mockDistributedTransaction,expectedResult,new PutIfNotExists())
      result mustBe expectedResult
      result mustBe TagPartitions(existingTag,Set(newTagParitionInfo))
    }

The various mocks are defined as

val mockCredentialsProvider = mock(classOf[CredentialsProvider])
  val mockUserTokenTransactionRepository = mock(classOf[UserTokenTransactionRepository])
  val mockUserTransactionRepository = mock(classOf[UserTransactionRepository])
  val mockUserProfileAndPortfolioTransactionRepository = mock(classOf[UserProfileAndPortfolioTransactionRepository])
  val mockHelperMethods = mock(classOf[HelperMethods])
  val mockTransactionService = mock(classOf[TransactionService])
  val mockQuestionsCreatedByUserRepo = mock(classOf[QuestionsCreatedByAUserForATagTransactionRepository])
  val mockQuestionsAnsweredByUserRepo = mock(classOf[QuestionsAnsweredByAUserForATagTransactionRepository])
  val mockDistributedTransaction = mock(classOf[DistributedTransaction])
  val mockQuestionTransactionDBService = mock(classOf[QuestionsTransactionDatabaseService])
  val mockQuestionNonTransactionDBService = mock(classOf[QuestionsNonTransactionDatabaseService])
  val mockAnswersTransactionRepository = mock(classOf[AnswersTransactionRepository])
  val mockPartitionsOfATagTransactionRepository = mock(classOf[PartitionsOfATagTransactionRepository])
  val mockPracticeQuestionsTagsTransactionRepository = mock(classOf[PracticeQuestionsTagsTransactionRepository])
  val mockPracticeQuestionsTransactionRepository = mock(classOf[PracticeQuestionsTransactionRepository])
  val mockSupportedTagsTransactionRepository = mock(classOf[SupportedTagsTransactionRepository])
  val mockPartitionsOfATagRepository = mock(classOf[PartitionsOfATagRepository])

The test case passes even though I have not provided any mock for partitionsOfATagTransactionRepository.add. Should I get a NullPointer exception when the add method is called.

I was expecting that I would need to write something like doNothing().when(servicesTestEnv.mockPartitionsOfATagTransactionRepository).add(ArgumentMatchers.any[DistributedTransaction],ArgumentMatchers.any[TagPartitions],ArgumentMatchers.any[MutationCondition]) or when(servicesTestEnv.mockPartitionsOfATagTransactionRepository).add(ArgumentMatchers.any[DistributedTransaction],ArgumentMatchers.any[TagPartitions],ArgumentMatchers.any[MutationCondition]).thenReturn(...) for the test case to pass.


Solution

  • Mockito team made a decision to return default value for a method if no stubbing is provided.

    See: https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#stubbing

    By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.

    This decision was made consciously: if you are focusing on a different aspect of behaviour of method under test, and the default value is good enough, you don't need to specify it.

    Note that other mocking frameworks have taken opposite path - they raise an exception when unstubbed call is detected (for example: EasyMock).

    See EasyMock vs Mockito: design vs maintainability?