捕获 spock return 值并用于验证
Capture spock return value and use it for verification
如何测试是否使用特定的动态值 - createdPersonId 调用了模拟的 logService 方法?
@SpringBean
private LogService logService = Mock(LogService)
def "test if id is logged"() {
when:
createdPersonId = personService.savePerson(personRequest)
then:
1 * logService.logSavedId(_) // it works fine
1 * logService.logSavedId(createdPersonId) // it doesn't work, createdPersonId is expected to be null instead of real value
}
static class PersonService {
LogService logService
PersonRepository personRepository
int savePerson(PersonRequest personRequest) {
def id = UUID.randomUUID().toString()
PersonEntity personEntity = mapRequestToEntity(personRequest)
entity.id = id
personRepository.persist(personEntity)
logService.logSavedId(id)
return id
}
}
也许我可以捕获 PersonEntity?
我不想注入 UUID 提供程序只是为了生成 UUID 并在测试中模拟它。但是我可以 mock/stub personRepository(它是由 spring 注入的)。
不能在交互中使用createdPersonId
,因为then:
块中的模拟交互实际上被转换为在when:
块之前定义。您在那里遇到了自举或母鸡与鸡蛋的问题,请参阅 。在定义该方法调用中使用的模拟的所需行为和交互时,您不能使用被测方法调用的结果。
不过你可以这样做(抱歉,我不得不推测你在问题中没有显示的依赖性 class):
package de.scrum_master.Whosebug
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.Whosebug.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:引入 class 以创建 ID
这里我们将 ID 创建分解为专用的 class IdCreator
,这很容易模拟。它将 ID 创建与 PersonService
分离。不需要使用像 Spy(PersonService)
这样花哨的东西,一个带有注入 ID 创建者的普通服务实例就足够了。即使在生产使用中,也可以很容易地为其他对象 ID 重用 UUID 创建者,单独测试它,用 subclass 覆盖它,或者甚至为了不同的目的重构一个具有不同实现的接口。您可能认为这是过度设计,但我认为不是。解耦和可测试性是软件设计中必不可少的东西。
package de.scrum_master.Whosebug.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
}
}
}
如何测试是否使用特定的动态值 - createdPersonId 调用了模拟的 logService 方法?
@SpringBean
private LogService logService = Mock(LogService)
def "test if id is logged"() {
when:
createdPersonId = personService.savePerson(personRequest)
then:
1 * logService.logSavedId(_) // it works fine
1 * logService.logSavedId(createdPersonId) // it doesn't work, createdPersonId is expected to be null instead of real value
}
static class PersonService {
LogService logService
PersonRepository personRepository
int savePerson(PersonRequest personRequest) {
def id = UUID.randomUUID().toString()
PersonEntity personEntity = mapRequestToEntity(personRequest)
entity.id = id
personRepository.persist(personEntity)
logService.logSavedId(id)
return id
}
}
也许我可以捕获 PersonEntity?
我不想注入 UUID 提供程序只是为了生成 UUID 并在测试中模拟它。但是我可以 mock/stub personRepository(它是由 spring 注入的)。
不能在交互中使用createdPersonId
,因为then:
块中的模拟交互实际上被转换为在when:
块之前定义。您在那里遇到了自举或母鸡与鸡蛋的问题,请参阅
不过你可以这样做(抱歉,我不得不推测你在问题中没有显示的依赖性 class):
package de.scrum_master.Whosebug
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.Whosebug.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:引入 class 以创建 ID
这里我们将 ID 创建分解为专用的 class IdCreator
,这很容易模拟。它将 ID 创建与 PersonService
分离。不需要使用像 Spy(PersonService)
这样花哨的东西,一个带有注入 ID 创建者的普通服务实例就足够了。即使在生产使用中,也可以很容易地为其他对象 ID 重用 UUID 创建者,单独测试它,用 subclass 覆盖它,或者甚至为了不同的目的重构一个具有不同实现的接口。您可能认为这是过度设计,但我认为不是。解耦和可测试性是软件设计中必不可少的东西。
package de.scrum_master.Whosebug.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
}
}
}