【问题标题】:Grails: How to mock properties for domain object with non-domain abstract base classGrails:如何使用非域抽象基类模拟域对象的属性
【发布时间】:2015-12-16 13:23:44
【问题描述】:

我有一个带有域类 Monster 的 grails 2.2.4 应用程序:

class Monster {
    int aggression
}

我可以像这样模拟和测试它:

import spock.lang.*
class MonsterSpec extends Specification {
    def "property mocks work"() {
        given:
        def m = Mock(Monster)
        m.aggression >> 5

        expect:
        m.aggression == 10
    }
}

最近我决定给它一个抽象基类(而不是一个域对象本身),这样我就可以在我的许多Monster-like 类之间共享方法实现:

abstract class Entity {} // Not under /domain

class RefactoredMonster extends Entity {
    int aggression
}

但是随后一千个简单的测试都失败了,就像这样:

import spock.lang.*
class MonsterSpec extends Specification {
    def "property mocks work"() {
        given:
        def m = Mock(RefactoredMonster)
        m.aggression >> 10

        expect:
        m.getAggression() == 10 // This works

        and:
        m.aggression == 10 // This fails!  m.aggression is null!
    }
}

到底发生了什么?如果我制作Entity 混凝土,问题就会消失,但是当然我不能水合任何Monster 对象,因为Hibernate 不知道如何处理Entity(而且我不想制作@987654330 @ 一个域对象,虽然我想如果我真的必须这样做)。

我错过了什么?

【问题讨论】:

  • 你能发布你的堆栈跟踪吗..
  • 没有堆栈跟踪,测试只是失败,因为m.aggression 为空,而不是10 应该是这样。错误消息只是普通的“条件不满足”Spock 失败。

标签: grails spock


【解决方案1】:

问题是 GORM 期望超类是域类。

使用Grails 2.2.4 所具有的Groovy 2.0,您可以使用编译时mixins 向类添加方法。这允许在没有继承的情况下重用方法。

Entity 可以保留为非域类,但它必须是具体类。然后,不要子类化,而是将其用作 mixin。

@Mixin(Entity)
class RefactoredMonster {
    int aggression
}

另一种选择

正如你所说,由于你需要重写方法的能力,所以 Mixins 已经出局了。

从更高的层面来看,一个潜在的问题是架构/设计。继承旨在表示 is-a 关系(例如,狗是动物)。但是,当继承主要用作重用方法的一种方式时,它可能会导致……一团糟。

最好放弃继承而选择has-a(委托)。这将允许您重用行为并在需要时覆盖它。不幸的是,Groovy 2.0 不支持@Delegate。因此,以下示例将具有比 Groovy 2.4 中编码的相同内容更多的样板代码:

interface Flier {
    def fly();
}

class FlierImp {
    def fly() { "I'm fying! WOOT!" }
}

class RealDuck implements Flier {
    def flier

    RealDuck() {
        flier = new FlierImp() // Purposely not using injection
    }

    def fly() {
        flier.fly()
    }
}

class RubberDuck implements Flier {
    def fly() { "I don't fly" }
}

def duck = new RealDuck()
def rubberDuck = new RubberDuck()

assert duck.fly() == "I'm fying! WOOT!"
assert rubberDuck.fly() == "I don't fly"

在上面的示例中,RealDuckRubberDuck 代表域类(这就是我不注入传单的原因)。飞行行为由接口要求,并通过仅实现该行为的类 (FlierImp) 或直接实现,如RubberDuck 所示。

【讨论】:

  • 你确定是 GORM 有问题吗?只有我的嘲笑对象坏了;我使用真实对象的集成测试都运行完美。无论如何,我希望 Monster 能够覆盖 Entity 中的方法,因此不能选择 Groovy 的 mixins。
  • 嗯,单元测试不使用真正的 GORM 实现,所以有时会发生意想不到的事情。我对这个实现知之甚少,不知道继承如何影响它。我添加了一个避免继承并使用接口的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-14
  • 2016-12-05
  • 1970-01-01
相关资源
最近更新 更多