为什么我认为我没有提供正确的模拟,但测试用例仍然通过

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

我正在测试这个功能。对我来说主要的一点是调用存储库的 add 方法 (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
  }

我写了这个测试用例来测试方法

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

各种mocks定义为

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])

即使我没有为 partitionsOfATagTransactionRepository.add 提供任何 mock,测试用例也通过了。当调用 add 方法时,我是否应该得到 NullPointer 异常。

我原以为我需要编写类似 doNothing().when(servicesTestEnv.mockPartitionsOfATagTransactionRepository).add(ArgumentMatchers.any[DistributedTransaction],ArgumentMatchers.any[TagPartitions],ArgumentMatchers.any[MutationCondition])when(servicesTestEnv.mockPartitionsOfATagTransactionRepository).add(ArgumentMatchers.any[DistributedTransaction],ArgumentMatchers.any[TagPartitions],ArgumentMatchers.any[MutationCondition]).thenReturn(...) 的内容才能使测试用例通过。

Mockito 团队决定 return 如果没有提供存根,方法的默认值。

参见: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.

这个决定是有意识地做出的:如果您关注被测方法行为的不同方面,并且默认值足够好,则不需要指定它。

请注意,其他模拟框架采取了相反的路径 - 它们在检测到未存根调用时引发异常(例如:EasyMock)。

EasyMock vs Mockito: design vs maintainability?