【问题标题】:Mocking static get gorm method in Grails unit test在 Grails 单元测试中模拟静态 get gorm 方法
【发布时间】:2014-04-28 22:56:08
【问题描述】:

我正在使用 Grails 2.3.7。我有一个简单的 Person 域对象:

class Person {
  String name
  static constraints = {}
}

和控制器:

@Transactional(readOnly = true)
class PersonController {
  def index() {
    render view:'index', model:[personList:Person.list()]
  }

  def show(Long id) {
    def person = Person.get(id)
    render view:'show', model:[person:person]
  }
  ...
}

我正在尝试为控制器编写一个单元测试来执行show 方法,但我不确定我需要如何配置测试以确保静态调用Person.get(id) 返回一个值。

这是我的(失败的)尝试:

import grails.test.mixin.*
import spock.lang.*

@TestFor(PersonController)
@Mock(Person)
class PersonControllerSpec extends Specification {

  void "Test show action loads Person by id and includes that person in the model rendered"() {
    setup:
      def personMockControl = mockFor(Person)
      personMockControl.demand.static.get() { int id -> new Person(name:"John") }

    when:"A valid person id passed to the show action"
      controller.show(123)

    then:"A model is populated containing the person domain instance loaded by that unique id"
      model.person != null
      model.person.name == "John"
  }
}

此测试失败,因为条件 model.person != null 失败。如何重写此测试以使其通过?

编辑

在这种情况下,我可以通过重做测试来避开静态方法调用:

void "Test show action loads Person by id and includes that person in the model rendered"() {
  setup:
    def p = new Person(name:"John").save()

  when:"A valid person id passed to the show action"
    controller.show(p.id)

  then:"A model is populated containing the person domain instance loaded by that unique id"
    model.person != null
    model.person.id == p.id
    model.person.name == "John"
}

但是,我真的很想了解如何正确模拟 Person 的静态 get 方法。

编辑 2

这是另一种情况来证明我认为需要模拟 get 方法。鉴于此过滤器代码:

def fitlers = {
  buyFilter(controller:"paypal", action:"buy") {
    after = {
      new VendorPayment(payment:request.payment, vendor: Vendor.get(params.buyerId)).save()
    }
  }
}

我正在尝试使用以下测试测试此过滤器是否按预期工作:

void "test the buyFilter to ensure it creates a VendorPayment when the PayPal plugin controller is called" () {
  setup:
  // how do I mock the Vendor.get(params.buyerId)
  // to return a vendor in order to save a new VendorPayment

  when:
  withFilters(action:"buy") {
    controller.buy()
  }

  then:
  VendorPayment.count() == 1
}

【问题讨论】:

  • 为什么需要模拟get方法?您已经在测试中模拟了 Person 并创建了一个模拟的 Person 实例。让 GORM 在show 中提取所需的 Person 实例?有帮助吗?
  • 我试图编造一个需要模拟get 方法的简单示例,也许这个示例过于简单。我将编辑问题以添加更复杂的情况。

标签: unit-testing grails mocking


【解决方案1】:

就像 dmahaptro 提到的,你不需要 嘲笑你的人。通过.get() 在测试中获取对象的首选/正确方法是您拥有它的方式——您创建域实例,并将其 id 传递给控制器​​操作以供使用。

但是,如果您需要像这样模拟静态方法,有两种方法:

1) 使用GrailsUnitTestMixin提供的mockFor方法,然后像这样指定静态方法:

GrailsMock mockPerson = mockFor(Person)
mockPerson.demand.static.get() { Long id -> new Person(name:"John") }

请注意,在您的情况下,模拟的静态 get 方法不包含正确的参数类型(使用 int 而不是 Long)因此没有调用此方法。

2) 修改类'metaClass。强烈建议不要更改 metaClass(尤其是当 @Mock 注释已经为您设置好所有内容时),并且可能导致测试泄漏(即,在您更改 metaClass 后,其他测试将无法正常工作,除非您恢复原始测试完成后的元类)。

也就是说,为了熟悉起见,模拟.get() 方法的方式是:

Person.metaClass.static.get = { Long id -> return personWithId }

同样,这被认为是不好的做法,应该避免,但是你有它。

【讨论】:

  • 感谢您的回答,尤其是有关 grails 测试的“最佳实践”的指导。我将确保避免依赖模拟这种类型的静态方法,但是,在设置块中使用以下内容仍会导致测试失败(model.person 为空):Person.metaClass.static.get = { id -> new Person(name:"John") } 这也失败了:Person.metaClass.static.get = { id -> new Person(name:"John").save() }为什么在这种情况下仍然失败?
  • @Brice 它是如何失败的?我在id 参数中添加了一个类型,这可能会有所帮助。
  • 是的!参数上缺少类型让我很生气!所以现在这行得通,另外这也行得通:mockFor(Person).demand.static.get() {Long id -> new Person(name:"John")}
  • @Brice Cool,很高兴为您提供帮助。是的,这是 Grails 2 中的一个变化(我相信)——在更改 metaClass 时,类型必须完全匹配。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-04
  • 1970-01-01
相关资源
最近更新 更多