捕获 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
    }
  }

}