ScalaMock 和蛋糕模式 - 为什么我的存根没有被调用?
ScalaMock and the Cake Pattern - Why is my stub not called?
我有一个使用 Cake 模式的 Scala 应用程序:
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 从何而来。我已经根据 damirv 在 this question 中的回答成功地修改了我的测试,但我希望这里有一些见解可以帮助我更好地理解 为什么我实际上并没有嘲笑我认为我对测试上面?
顺便说一下,我是从 C# 背景来到 Scala 的。
更新:根据下面的答案,这个有效:
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)
}
def repository: Repository
这是一种每次调用时 return 都会创建一个新存根的方法。尝试将其设为 val
而不是 return 每个 invocation/access:
上的相同存根
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]
}
// ...
}
此外,您正在使用数组作为参数类型。请注意 Scala 中的 Array("foo") != Array("foo")
,因此我建议您在使用数组 (findRecord
) 的 when
调用中使用 argThat
匹配器 - 参见此处:Unable to create stub with Array argument in ScalMock
或尝试谓词匹配,如下所述:http://scalamock.org/user-guide/matching/
我有一个使用 Cake 模式的 Scala 应用程序:
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 从何而来。我已经根据 damirv 在 this question 中的回答成功地修改了我的测试,但我希望这里有一些见解可以帮助我更好地理解 为什么我实际上并没有嘲笑我认为我对测试上面?
顺便说一下,我是从 C# 背景来到 Scala 的。
更新:根据下面的答案,这个有效:
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)
}
def repository: Repository
这是一种每次调用时 return 都会创建一个新存根的方法。尝试将其设为 val
而不是 return 每个 invocation/access:
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]
}
// ...
}
此外,您正在使用数组作为参数类型。请注意 Scala 中的 Array("foo") != Array("foo")
,因此我建议您在使用数组 (findRecord
) 的 when
调用中使用 argThat
匹配器 - 参见此处:Unable to create stub with Array argument in ScalMock
或尝试谓词匹配,如下所述:http://scalamock.org/user-guide/matching/