ScalaMock и Cake Pattern - Почему моя заглушка не называется?

У меня есть приложение Scala, которое использует шаблон Cake:

trait RepositoryComponent {
  def repository: Repository

  trait Repository {
    def shouldSave(record: GenericRecord): Boolean
    def findRecord(keys: Array[String]): Long
    def insertRecord(record: GenericRecord)
    def updateRecord(keys: Array[String], record: GenericRecord)
    def cleanUp()
  }
}

trait DbRepositoryComponent extends RepositoryComponent with Logged {
  val connection: Connection
  val subscription: Subscription
  val schema: Schema

  def repository = new DbRepository(connection, subscription, schema)

  class DbRepository(connection: Connection, subscription: Subscription, schema: Schema) extends Repository {
    ...
  }

trait ConsumerComponent {
  def consumer: Consumer

  trait Consumer {
    def write(keys: Array[String], record: GenericRecord)
    def close()
  }
}

trait DbReplicatorComponent extends ConsumerComponent with Logged {
  this: RepositoryComponent =>

  def consumer = new DatabaseReplicator()

  class DatabaseReplicator extends Consumer {
    ...
  }

Когда я наивно начал тестировать реализацию consumer.writeЯ пробовал что-то вроде этого (используя ScalaMock):

class DbReplicatorComponentTests extends FunSuite with MockFactory {

  private val schema = new Schema.Parser().parse(getClass.getResourceAsStream("/AuditRecord.avsc"))
  private val record = new GenericRecordBuilder(schema)
    .set("id1", 123)
    .set("id2", "foo")
    .set("text", "blergh")
    .set("audit_fg", "I")
    .set("audit_ts", 1498770000L)
    .build()

  test("A record should be inserted if the audit flag is 'I' and no existing record is found") {
    val replicator = new DbReplicatorComponent with RepositoryComponent {
      override def repository: Repository = stub[Repository]
    }

    (replicator.repository.shouldSave _).when(record).returns(true)
    (replicator.repository.findRecord _).when(Array("123", "foo")).returns(0L)

    replicator.consumer.write(Array("123", "foo"), record)

    (replicator.repository.insertRecord _).verify(record)
  }
}

Тест не пройден, потому что я не заглушаю фактическую реализацию:

Unsatisfied expectation:

Expected:
inAnyOrder {
  <stub-1> Repository.shouldSave({"id1": 123, "id2": "foo", "text": "blergh", "audit_fg": "I", "audit_ts": 1498770000}) any number of times (never called)
  <stub-2> Repository.findRecord([Ljava.lang.String;@4b8ee4de) any number of times (never called)
  <stub-4> Repository.insertRecord({"id1": 123, "id2": "foo", "text": "blergh", "audit_fg": "I", "audit_ts": 1498770000}) once (never called - UNSATISFIED)
}

Actual:
  <stub-3> Repository.shouldSave({"id1": 123, "id2": "foo", "text": "blergh", "audit_fg": "I", "audit_ts": 1498770000})
ScalaTestFailureLocation: com.generalmills.datalake.sql.DbReplicatorComponentTests at (DbReplicatorComponentTests.scala:12)
org.scalatest.exceptions.TestFailedException: Unsatisfied expectation:

Эта проблема подчеркивает тот факт, что я действительно не люблю Scala. Я понятия не имею, откуда взялся stub-3. Я успешно переработал свои тесты, следуя ответу Дамирва на этот вопрос, но я надеюсь, что здесь есть некоторое понимание, которое поможет мне лучше понять, почему я на самом деле не высмеиваю то, что я думаю о тесте выше?

Fwiw, я прихожу в Scala из фона C#.

ОБНОВЛЕНИЕ: основываясь на ответе ниже, это работает:

test("A record should be inserted if the audit flag is 'I' and no existing record is found") {
    val replicator = new DbReplicatorComponent with RepositoryComponent {
      val _repo = stub[Repository]
      override def repository: Repository = {
        _repo
      }
    }

    (replicator.repository.shouldSave _).when(record).returns(true)
    (replicator.repository.findRecord _).when(Array("123", "foo")).returns(0L)

    replicator.consumer.write(Array("123", "foo"), record)

    (replicator.repository.insertRecord _).verify(record)
  }

1 ответ

Решение

def repository: Repository это метод, который возвращает новую заглушку каждый раз, когда вы ее вызываете. Попробуйте сделать это val вместо этого возвращать одну и ту же заглушку при каждом вызове / доступе:

test("A record should be inserted if the audit flag is 'I' and no existing record is found") {
  val replicator = new DbReplicatorComponent with RepositoryComponent {
    override val repository: Repository = stub[Repository]
  }
  // ...
}

Кроме того, вы используете массив в качестве типа параметра. Обратите внимание, что Array("foo") != Array("foo") в Scala, поэтому я предлагаю вам использовать argThat совпадение в вашем when позвоните, где вы используете массив (findRecord) - см. здесь: невозможно создать заглушку с аргументом Array в ScalMock

Или попробуйте Predicate Matching, как описано здесь: http://scalamock.org/user-guide/matching/

Другие вопросы по тегам