您不能在交互中使用createdPersonId,因为then: 块中的模拟交互实际上已转换为在when: 块之前定义。你有一个引导或母鸡与鸡蛋的问题,请参阅my other answer。在定义该方法调用中使用的模拟的所需行为和交互时,您不能使用被测方法调用的结果。
不过,你可以做这样的事情(对不起,我不得不推测你的问题中没有显示的依赖类):
package de.scrum_master.stackoverflow
import spock.lang.Specification
class PersonServiceTest extends Specification {
private LogService logService = Mock(LogService)
def "test if id is logged"() {
given:
def person = new Person(id: 11, name: "John Doe")
def personRequest = new PersonRequest(person: person)
def personService = new PersonService(logService: logService)
def id = personRequest.person.id
when:
def createdPersonId = personService.savePerson(personRequest)
then:
1 * logService.logSavedId(id)
createdPersonId == id
}
static class Person {
int id
String name
}
static class PersonRequest {
Person person
}
static class LogService {
void logSavedId(int id) {
println "Logged ID = $id"
}
}
static class PersonService {
LogService logService
int savePerson(PersonRequest personRequest) {
def id = personRequest.person.id
logService.logSavedId(id)
return id
}
}
}
更新:以下是重构应用程序以使其更具可测试性的两种变体。
变体 1:介绍创建 ID 的方法
在这里,我们将 ID 创建分解到一个受保护的方法 createId() 中,然后我们可以在称为 Spy 的部分模拟中存根该方法:
package de.scrum_master.stackoverflow.q60829903
import spock.lang.Specification
class PersonServiceTest extends Specification {
def logService = Mock(LogService)
def "test if id is logged"() {
given:
def person = new Person(name: "John Doe")
def personRequest = new PersonRequest(person: person)
and:
def personId = "012345-6789-abcdef"
def personService = Spy(PersonService) {
createId() >> personId
}
personService.logService = logService
when:
personService.savePerson(personRequest)
then:
1 * logService.logSavedId(personId)
person.id == personId
}
static class Person {
String id
String name
}
static class PersonRequest {
Person person
}
static class LogService {
void logSavedId(String id) {
println "Logged ID = $id"
}
}
static class PersonService {
LogService logService
String savePerson(PersonRequest personRequest) {
def id = createId()
personRequest.person.id = id
logService.logSavedId(id)
return id
}
protected String createId() {
return UUID.randomUUID().toString()
}
}
}
变体 2:为创建 ID 引入类
在这里,我们将 ID 创建分解为一个易于模拟的专用类 IdCreator。它将 ID 创建与PersonService 分离。不需要使用像Spy(PersonService) 这样的花哨的东西,一个带有注入 ID 创建者的普通服务实例就足够了。即使在生产使用中,也很容易将 UUID 创建器重新用于其他对象 ID,单独测试它,通过子类覆盖它,甚至重构为具有不同实现的接口以用于不同目的。你可能认为这是过度设计,但我认为不是。解耦和可测试性是软件设计中必不可少的东西。
package de.scrum_master.stackoverflow.q60829903
import spock.lang.Specification
class PersonServiceTest extends Specification {
def logService = Mock(LogService)
def "test if id is logged"() {
given:
def person = new Person(name: "John Doe")
def personRequest = new PersonRequest(person: person)
and:
def personId = "012345-6789-abcdef"
def idCreator = Stub(IdCreator) {
createId() >> personId
}
def personService = new PersonService(logService, idCreator)
when:
personService.savePerson(personRequest)
then:
1 * logService.logSavedId(personId)
person.id == personId
}
static class Person {
String id
String name
}
static class PersonRequest {
Person person
}
static class LogService {
void logSavedId(String id) {
println "Logged ID = $id"
}
}
static class IdCreator {
String createId() {
return UUID.randomUUID().toString()
}
}
static class PersonService {
LogService logService
IdCreator idCreator
PersonService(LogService logService) {
this(logService, new IdCreator())
}
PersonService(LogService logService, IdCreator idCreator) {
this.logService = logService
this.idCreator = idCreator
}
String savePerson(PersonRequest personRequest) {
def id = idCreator.createId()
personRequest.person.id = id
logService.logSavedId(id)
return id
}
}
}