Groovy中Mock Spark DataFrameReader.option方法时遇空指针异常求助
问题场景
我在使用Spark读取CSV文件时,对表头(header)和分隔符(sep)做参数化处理,编写的Java代码如下:
DataFrameReader dataFrameReader = spark.read(); dataFrameReader = "csv".equalsIgnoreCase(params.getReadFileType()) ? dataFrameReader .option("sep",params.getDelimiter()) .option("header",params.isHeader()) :dataFrameReader;
刚接触Groovy,在Mock DataFrameReader的option方法时遇到空指针异常,编写的Mock代码如下:
DataFrameReader dfReaderLoader = Mock(DataFrameReader) DataFrameReader dfReaderOptionString = Mock(DataFrameReader) DataFrameReader dfReaderOptionBoolean = Mock(DataFrameReader) SparkSession sparkSession = Mock(SparkSession) sparkSession.read() >> dfReaderLoader dfReaderLoader.option(_ as String, _ as String) >> dfReaderOptionString dfReaderOptionString.option(_ as String, _ as Boolean) >> dfReaderOptionBoolean
运行后报错:
java.lang.NullPointerException: Cannot invoke "org.apache.spark.sql.DataFrameReader.option(String, boolean)" because the return value of "org.apache.spark.sql.DataFrameReader.option(String, String)" is null
解决方案
问题根源
DataFrameReader的option方法是链式调用设计,每次调用都会返回一个可继续操作的DataFrameReader实例。你的Mock代码只针对特定参数类型的调用做了返回,但如果参数匹配失败、或者后续有未覆盖的调用,就会返回null,触发空指针异常。
方案1:复用单个Mock实例(推荐)
让同一个Mock实例支持所有类型的option调用,且每次调用都返回自身,完美适配链式调用逻辑:
SparkSession sparkSession = Mock(SparkSession) DataFrameReader dfReader = Mock(DataFrameReader) // 关联spark.read()到mock实例 sparkSession.read() >> dfReader // 处理String类型参数的option调用,返回自身 dfReader.option(_ as String, _ as String) >> dfReader // 处理Boolean类型参数的option调用,返回自身 dfReader.option(_ as String, _ as Boolean) >> dfReader
这种方式简单直接,不管链式调用多少次option,都不会出现null的情况。
方案2:分阶段Mock(适用于需要验证调用顺序的场景)
如果需要严格区分不同阶段的option调用(比如验证先调用sep再调用header),可以分实例Mock,但要确保每个阶段的Mock实例都能处理所有可能的调用,避免返回null:
DataFrameReader dfReaderLoader = Mock(DataFrameReader) DataFrameReader dfReaderOptionString = Mock(DataFrameReader) DataFrameReader dfReaderOptionBoolean = Mock(DataFrameReader) SparkSession sparkSession = Mock(SparkSession) sparkSession.read() >> dfReaderLoader // 匹配指定参数的调用,返回对应阶段的实例 dfReaderLoader.option("sep", _ as String) >> dfReaderOptionString dfReaderOptionString.option("header", _ as Boolean) >> dfReaderOptionBoolean // 添加默认匹配:所有未指定的option调用都返回自身,防止null dfReaderLoader.option(_, _) >> dfReaderLoader dfReaderOptionString.option(_, _) >> dfReaderOptionString dfReaderOptionBoolean.option(_, _) >> dfReaderOptionBoolean
这种方式既可以验证特定参数的调用顺序,又能避免因未覆盖的调用返回null导致的异常。
内容的提问来源于stack exchange,提问作者Observer

