scala - 使用 ScalaMock 测试具有隐式类的类
问题描述
假设我有一个包含在 Try 块中的读取操作的特征:
import scala.util.Try
trait ReadingProvider[T] {
def readTable(tableName: String):Try[T]
}
还有一个提供使用 spark 读取的方法的类和一个用于从故障中恢复的方法的隐式类
import org.apache.spark.sql._
import org.apache.spark.sql.types.StructType
import scala.util.{Try, Success, Failure}
class SparkReadingProvider(spark: SparkSession) extends ReadingProvider[DataFrame] {
override readTable: Try[DataFrame] = Try(spark.read.table(tableName))
def createEmptyDF(schema: StructType): DataFrame =spark.createDataFrame(spark.sparkContext.emptyRDD[Row], schema)
}
implicit class ReadingHandler(tryDF: Try[DataFrame]) {
def recoverWithEmptyDF(schema: StructType): DataFrame = tryDF match {
case Failure(ex) => //Log something
createEmptyDF(schema)
case Success(df) => //Log something
df
}
}
}
现在我有一个包含阅读和一些转换的对象:
object MyObject {
def readSomeTable(tableName): SparkReadingProvider => DataFrame = provider => {
import provider.ReadingHandler
provider.readTable(tableName).recoverWithEmptyDF
}
def transform: DataFrame => DataFrame = ???
def mainMethod(tableName)(implicit val provider: SparkReadingProvider): DataFrame =
readSomeTable(tableName) andThen transform apply provider
}
我想对里面的方法进行单元测试MyObject
。我不想使用真实的文件或表格,因此我的目标是使用模拟。
在我的测试中,我试图模拟 SparkReadingProvider:
describe("reading") {
it("should return empty dataframe when reading failed") {
val provider: SparkReadingProvider = mock[SparkReadingProvider]
val tableName: String = "no_table"
provider.readTable _ expects tableName returning Failure(new Exception("Table does not exist"))
MyObject.readSomeTable(tableName) shouldBe empty
}
}
但是它失败并出现错误:
意外调用:<mock-1> SparkReadingProvider.ReaderHandler(Failure(java.lang.Exception: 表不存在))
预期:inAnyOrder { < mock-1> SparkReadingProvider.readTable(no_table) 一次(调用一次)}
实际:<mock-1> SparkReadingProvider.readTable(no_table) <mock-1> SparkReadingProvider.ReaderHandler(Failure(java.lang.Exception: 表不存在))
我的问题是:
- 是否有可能在当前设置中实现我想要的?
- 如果没有,我应该如何重构我的代码
readSomeTable
如果我在不同的类中测试隐式类中可用的方法,那么测试 the和mainMethod
inside是否有意义MyObject
?
解决方案
问题是扩展方法基本上是一种语法糖。
我将用一个例子来解释。
trait Foo
implicit class FooImplicit(foo: Foo) {
def bar: String = "bar
}
foo.bar
被翻译成
new FooImplicit(foo).bar
如此嘲讽:
Mockito.when(foo.bar).thenReturn("bad")
变成:
Mockito.when(new FooImplicit(foo).bar).thenReturn("bad")
请注意如何foo.bar
处理,这就是问题所在。
是否有可能在当前设置中实现我想要的?
不,我认为在当前设置中是不可能的。
如果没有,我应该如何重构我的代码
实现这一点的唯一方法是使用隐式转换而不是隐式类。
我将展示一个如何实现这一目标的示例:
trait Foo {
def bar: String
}
object ImplicitFoo {
object implicits {
implicit fooToFooImplicit(foo: Foo): FooOps = new FooImplicit(foo)
class FooImplicit(foo: Foo) {
def bar: String = "bar"
}
}
}
和你的测试
import org.scalatest.WordSpec
import org.mockito.MockitoSugar
class MySpec extends WordSpec with MockitoSugar {
"My mock" should {
"handle methods from implicit classes" in {
val fooOps = mock[FooImplicit]
implicit fooToOps(foo: Foo): FooImplicit = fooOps
val foo = mock[Foo]
when(foo.bar) thenReturn "bad" // works
}
}
}
在您的生产中,您需要获取形状的隐式参数,Foo => FooImplicit
因此当您从测试中调用该方法时,会提供实际的隐式模拟......
如果我在不同的类中测试隐式类中可用的方法,那么在 MyObject 中测试 readSomeTable 和 mainMethod 是否有意义?
我认为您不需要测试 MyObject 中的 readSomeTable 和 mainMethod。但其他方式是正确的。
让我知道它是否有帮助!
推荐阅读
- python - 在 QPlainTextEdit 元素中使用 Shift+Return 的 QShortcut 和 QKeySequence
- android - 模拟器无法启动(c066d201:未处理的退出 1d)
- vb.net - 公共函数返回 VB.Net
- python - 带复位的累积和
- android - 为什么在使用“flutter build aar”命令制作 aar 时,Android 项目中无法访问 dart 类?
- javascript - 设置 favicon 时不出现 React-FontAwesome 图标
- r - 用 survfit() 拟合生存曲线
- wpf - 您将如何在 WPF 中构建移动样式的 Shift 键?
- angular - 如何在单击反应形式的提交按钮时重置所有验证
- perl - 带有 U 选项和大写选项字符的音译