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

使用Mockito mock含原始类型的Scala case类时触发空指针异常

嘿,这个问题我之前踩过一模一样的坑!让我给你好好捋捋来龙去脉,还有可行的解决办法。

问题根源:Scala 原始类型 + Mockito 的字节码冲突

先还原下你的场景:
你定义了这样的 trait 和 case class:

trait HasId[T] { def id: T }
case class Entity(id: Long) extends HasId[Long]

然后在测试里尝试 mock Entity 并 stub id 属性:

val entityMock = mock[Entity]
Mockito.when(entityMock.id).thenReturn(0)

结果执行到第二行直接抛出了NPE,报错日志是:

An exception or error caused a run to abort.
java.lang.NullPointerException
at com.test.Entity$MockitoMock$1085095743.id(Unknown Source)
at com.test.Test.(Test.scala:23)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)...

为啥会这样?核心原因是Scala 对原始类型的编译处理和 Mockito 的 mocking 逻辑不兼容:

  • Scala 里的 Long 会被编译成 Java 的 long 原始类型,case class 的 id getter 方法返回值也是原始类型 long。
  • Mockito 在生成 mock 实例时,对于原始类型的方法,没法像引用类型那样返回 null 作为临时占位值——因为原始类型不能为 null。所以当你调用 entityMock.id 准备 stub 的时候,直接触发了 NPE,连 when(...) 逻辑都没走到。
  • 你换成 String 没问题,是因为 String 是引用类型,Mockito 可以返回 null 临时占位,后续的 thenReturn 能正常覆盖这个值。
可行的解决方案

方案1:换用 ScalaMock(最推荐)

Mockito 本来就是为 Java 设计的,对 Scala 的 case class、值类这些特性支持一直不太友好。ScalaMock 是专门为 Scala 打造的 mocking 框架,能完美处理这些 Scala 特有的语法,用它来 mock Entity 就不会有这个问题。

示例代码:

import org.scalamock.scalatest.MockFactory
import org.scalatest.flatspec.AnyFlatSpec

class EntityTest extends AnyFlatSpec with MockFactory {
  trait HasId[T] { def id: T }
  case class Entity(id: Long) extends HasId[Long]

  "Mocked Entity" should "return the specified id" in {
    val entityMock = mock[Entity]
    (entityMock.id _).expects().returning(0L)
    assert(entityMock.id == 0L)
  }
}

方案2:把原始类型换成包装类型

如果不想换框架,可以把 Entity 里的 Long 换成 Java 的 java.lang.Long(或者 Scala 的 Option[Long],如果业务允许 id 为空的话),这样 getter 方法返回的是引用类型,Mockito 可以安全返回 null 作为临时值,后续的 thenReturn 也能正常生效:

// 修改后的 Entity
case class Entity(id: java.lang.Long) extends HasId[java.lang.Long]

// 测试代码不变,正常运行
val entityMock = mock[Entity]
Mockito.when(entityMock.id).thenReturn(0L)

方案3:Mock trait 而非具体的 case class

既然 Entity 实现了 HasId[Long],你可以直接 mock 这个 trait,绕开 case class 的原始类型 getter 问题:

val idMock = mock[HasId[Long]]
Mockito.when(idMock.id).thenReturn(0L)

// 如果业务代码需要 Entity 类型,可以做类型转换(前提是代码依赖的是 HasId 而非具体 Entity)
val entity: Entity = idMock.asInstanceOf[Entity]

不过这个方案有局限性,如果你的代码必须依赖 Entity 具体类型,那可能不太适用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 03:20:53