Тестовый класс с неявным классом с использованием ScalaMock - PullRequest
0 голосов
/ 06 марта 2020

Предположим, у меня есть черта с операциями чтения, заключенными в блок 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
    }
}

Однако это не удается с ошибкой:

Неожиданный вызов: SparkReadingProvider.ReaderHandler (Ошибка (java .lang.Exception: таблица не существует))

Ожидается: inAnyOrder { SparkReadingProvider.readTable (no_table) один раз (вызывается один раз)}

Actual: SparkReadingProvider.readTable (no_table) SparkReadingProvider.ReaderHandler (Ошибка (* 1039) * .lang.Exception: таблица не существует))

Мои вопросы:

  • Можно ли добиться того, чего я хочу в текущей настройке?
  • Если нет, то как мне провести рефакторинг моего кода
  • Если я тестирую в другом классе методы, доступные в неявном классе, имеет ли смысл тестировать readSomeTable и mainMethod внутри MyObject?

1 Ответ

2 голосов
/ 06 марта 2020

Проблема заключается в том, что методы расширения в основном являются синтаксисом c сахара.

Я объясню на примере.

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 лечится и в этом проблема.

Можно ли добиться того, что я хочу в текущей настройке?

Нет, я не думаю, что это возможно в текущей setup.

Если нет, то как мне выполнить рефакторинг моего кода

Единственный способ добиться этого - использовать неявные преобразования, а не неявные классы.

Я покажу пример, как этого можно достичь:

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, поэтому при вызове этого метода из теста предоставляется фактическая неявная имитация ...

Если я тестирую в другом классе методы, доступные в неявном классе, он делает смысл тестировать readSomeTable и mainMethod внутри MyObject?

Я не думаю, что вам требуется тестировать readSomeTable и mainMethod внутри MyObject.B, но наоборот - true.

Дайте мне знать, если это поможет !!

...