You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala Mockito单元测试报方法未正确桩化空指针异常

Scala Mockito单元测试空指针异常排查

问题场景

在Scala项目中使用Mockito框架运行单元测试时抛出NullPointerException,核心报错提示NotificationService.sendMessage方法未被正确桩化,方法调用时传入的所有参数均为null。

测试用例代码

class MailerServiceSpec extends BaseSpec {
  val http: HttpExt = mock[HttpExt]
  val mailService: NotificationService = mock[NotificationService]
  val rawJobResponse: JsObject = Json.obj(("aman","verma"))

  "Send Emails to multiple/single user" should {
    "send email to user" in {
      when(http.singleRequest(any[HttpRequest], any[HttpsConnectionContext], any[ConnectionPoolSettings], any[LoggingAdapter])) thenReturn
        future(HttpResponse(status = StatusCodes.OK, entity = httpEntity(rawJobResponse)))
      whenReady(mailService.sendMessage(any[String],any[String],any[String])(any[ExecutionContext])){ result =>
        assert(result == Done)
      }
    }
  }
}

被测业务代码(NotificationService.scala)

override def sendMessage(receivers: List[String], subject: String, content: String)(implicit
    ec: ExecutionContext
  ): Future[Done] = {

    val notification = Notification(
      recipients = receivers,
      cc = Nil,
      bcc = Nil,
      sender = sender,
      htmlBody = content,
      htmlTitle = subject
    )

    val httpHeader = RawHeader("SENDGRID_API_KEY", sendgridApiKey)
    (for {
      entity <- Marshal(notification).to[MessageEntity]
      _ <- makeHttpRequest(
        HttpRequest(POST, notificationEmailUrl, entity = entity).addHeader(httpHeader)
      )
    } yield Done).andThen {
      case Failure(exception) => logger.error("Failed to send email", exception)
    }

  }

报错信息

You have a NullPointerException here:
-> at org.scalatest.concurrent.ScalaFutures$$anon$1.futureValueImpl(ScalaFutures.scala:294)
because this method call was *not* stubbed correctly:
-> at scala.Option.orElse(Option.scala:477)
notificationService.sendMessage(
    null,
    null,
    null,
    null
);

问题根因

代码存在三个核心错误:

  • 错误mock了被测类本身:测试目标是验证NotificationService.sendMessage的业务逻辑,但是代码里直接mock了NotificationService的实例,mock对象的所有方法默认不会执行真实业务逻辑,只会返回对应类型的默认值(对象类型默认返回null)。
  • 参数匹配器使用位置错误:any[T]()是Mockito的参数匹配器,只能用在when(...)打桩语句的括号内,用来声明打桩规则匹配的参数范围,不能在实际调用业务方法传参时使用。在打桩上下文之外调用any[T]()会直接返回null,这也是报错里出现四个null入参的直接原因。
  • 参数类型不匹配:sendMessage第一个入参类型是List[String],测试代码里写的any[String]类型完全不匹配,即使打桩也无法命中对应方法。

修复方案

  • 仅mock被测类依赖的外部组件(比如示例中的HttpExt),不要mock被测服务本身。应当通过构造函数传入mock依赖,实例化真实的NotificationService对象,保证测试时执行真实业务逻辑。
  • 调用被测方法时传入真实的测试参数(比如测试用收件人列表、邮件主题、测试用ExecutionContext),禁止在真实调用传参时使用any类匹配器。
  • 打桩时注意参数类型和方法定义保持一致,避免类型不匹配导致打桩失效。

修复后参考代码

class MailerServiceSpec extends BaseSpec {
  // 仅mock外部依赖组件
  val http: HttpExt = mock[HttpExt]
  // 实例化真实的被测服务,传入mock依赖和测试配置
  val mailService: NotificationService = new NotificationService(
    http = http,
    sendgridApiKey = "test-key",
    notificationEmailUrl = "http://test-url/mail"
    // 其余构造参数传入测试环境默认值即可
  )
  val rawJobResponse: JsObject = Json.obj(("status","ok"))

  "Send Emails to multiple/single user" should {
    "send email to user" in {
      // 仅对外部依赖的调用打桩
      when(http.singleRequest(any[HttpRequest], any[HttpsConnectionContext], any[ConnectionPoolSettings], any[LoggingAdapter])) thenReturn
        Future.successful(HttpResponse(status = StatusCodes.OK, entity = httpEntity(rawJobResponse)))
      
      // 传入真实测试参数调用被测方法
      val sendFuture = mailService.sendMessage(
        receivers = List("test@example.com"),
        subject = "测试通知",
        content = "测试邮件内容"
      )
      
      whenReady(sendFuture){ result =>
        assert(result == Done)
      }
    }
  }
}

内容的提问来源于stack exchange,提问作者Aman Verma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 04:01:23