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
相关产品推荐
相关产品推荐

