【问题标题】:Why does a method parameter cause NotSerializableException with Mockito?为什么方法参数会导致 Mockito 出现 NotSerializableException?
【发布时间】:2023-03-02 22:32:02
【问题描述】:

我有一些使用 spark 运行的 Scala 代码,但我对其进行了简化:

// Not Serializable
class Config

object FileReader extends FileReader
class FileReader extends Serializable {
  def read(config: Config): String = config.getClass.toString
}

object Task extends Task(FileReader)
class Task(fileReader: FileReader) extends Serializable {
  def execute(config: Config): Unit = {
    fileReader.read(config)
  }
}

不是Config 不是Serializable

我想为它们写一些测试,Task 的实例需要是Serializable,因为它可能被序列化并发送给 spark worker。

我使用这个函数来检查一个对象是否可以被序列化:

def checkSerializable(obj: AnyRef, name: String) = {
  println("### checking " + name + ": " + obj.getClass)
  new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(obj)
  println(name + " ok")
}

首先我们检查一个普通的Task实例是否可以序列化:

val task = new Task(new FileReader)
checkSerializable(task, "no-mockito")

输出:

### checking no-mockito: class Task
no-mockito ok

看起来不错。

但我想用 Mockito 模拟 FileReader,所以我的代码是:

val fileReader = Mockito.mock(classOf[FileReader])
val config = Mockito.mock(classOf[Config])
Mockito.when(fileReader.read(config)).thenReturn("mocked")
val task = new Task(fileReader)
checkSerializable(task, "with-mockito1")

报错Config不可序列化:

### checking with-mockito1: class Task
java.io.NotSerializableException: Config$$EnhancerByMockitoWithCGLIB$$c7dcb0a5
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
    at java.io.ObjectOutputStream.writeArray(ObjectOutputStream.java:1359)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1155)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)

这很奇怪,因为config 只是一些方法参数,而不是类字段!

我稍微修改了我的代码以使 config 可序列化:

val fileReader = Mockito.mock(classOf[FileReader])
val config = Mockito.mock(classOf[Config], Mockito.withSettings().serializable())
Mockito.when(fileReader.read(config)).thenReturn("mocked")
val task = new Task(fileReader)
checkSerializable(task, "with-mockito2")

它仍然失败,另一个NotSerializableException

### checking with-mockito2: class Task
java.io.NotSerializableException: org.mockito.internal.creation.DelegatingMethod
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1165)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1535)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1413)

不确定为什么在使用 Mockito 时 config 实例会包含在 task 中?又该如何避免呢?

演示项目:https://github.com/freewind/mockito-serialization-issue,你可以直接克隆它并运行demo/MockitoDemo.scala


更新:

另一个值得注意的是:如果我删除了这一行

fileReader.read(config)

来自Task,这意味着Task 将是:

class Task(fileReader: FileReader) extends Serializable {
  def execute(config: Config): Unit = {
    // removed this line: fileReader.read(config)
  }
}

不会再抛出NotSerializableException(我没有更改测试代码)

【问题讨论】:

  • 您正在尝试序列化一个Task,该Task 已填充了 mock fileReader,并且此模拟 fileReader 已存储有关其读取方法为时应执行的操作的信息使用模拟配置调用。当您序列化该任务时,需要对模拟 fileReader 进行序列化,这将不起作用,因为它保留了“何时”信息(第一次失败),并且因为这些模拟无论如何都具有不可序列化的东西(第二次失败)。不确定有什么方法可以像这样混合模拟和序列化。
  • @Shadowlands,刚刚更新了我的问题,又一个奇怪的事情
  • Re: 更新 - 在这一点上,模拟似乎既不包含对 config 的引用(尽管测试代码中的 when 行现在不应该编译,如果你没有'不要删除它),也不是它之前拥有的DelegatingMethod实体。如果您在 fileReader 中添加任何方法,则可能会返回后一个问题。
  • @Shadowlands 感觉自己的更新没说清楚,所以又更新了一次

标签: scala serialization apache-spark mockito


【解决方案1】:

您可能还需要使 FileReader 模拟可序列化尝试:

val fileReader = Mockito.mock(classOf[FileReader], Mockito.withSettings().serializable())

【讨论】:

猜你喜欢
  • 2011-10-17
  • 2019-10-15
  • 1970-01-01
  • 1970-01-01
  • 2012-02-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多