Groovy Spock模拟不适用于单元测试

Groovy Spock模拟不适用于单元测试,groovy,spock,Groovy,Spock,我从Spock单元测试中得到了奇怪的结果,我认为这是由于误用Groovy的注释造成的。然而,多亏了另一位用户的帮助,我发现斯波克创建模拟的方式存在问题。虽然我已经通过用真实实例替换注入的mock解决了这个问题,但实际上我需要让mock在这里工作 我的主要课程: @Canonical @TupleConstructor(callSuper = true) abstract class Vehicle { Long id } @Canonical @TupleConstructor(cal

我从Spock单元测试中得到了奇怪的结果,我认为这是由于误用Groovy的注释造成的。然而,多亏了另一位用户的帮助,我发现斯波克创建模拟的方式存在问题。虽然我已经通过用真实实例替换注入的mock解决了这个问题,但实际上我需要让mock在这里工作

我的主要课程:

@Canonical
@TupleConstructor(callSuper = true)
abstract class Vehicle {
    Long id
}

@Canonical
@TupleConstructor(callSuper = true, includeSuperProperties = true)
abstract class Foobaz extends Vehicle {
    String name
    String label
    String description
}

@Canonical
@TupleConstructor(callSuper = true, includeSuperProperties = true)
class Fizz extends Foobaz {
    // This is an empty class that creates a meaningful name over the
    // abstract Foobaz parent class. This may seem like bad design in
    // this analogy, but I assure you it makes sense (from a Domain-Driven
    // Design perspective) in my actual application.
}

@Canonical
@TupleConstructor(callSuper = true, includeSuperProperties = true)
class Car extends Vehicle {
    Fizz fizz1
    Fizz fizz2

    @Override
    String toString() {
        "${fizz1.name} - ${fizz2.name}"
    }
}
我的斯波克测试:

class CarSpec extends Specification {
    def "toString() generates a correct string"() {
        given: "a Car with some mocked dependencies"
        String f1 = 'fizzy'
        String f2 = 'buzzy'
        Fizz fizz1 = Mock(Fizz)
        Fizz fizz2 = Mock(Fizz)

        fizz1.name >> f1
        fizz2.name >> f2

        Car car = new Car(1L, fizz1, fizz2)

        when: "we call toString()"
        String str = car.toString()

        then: "we get a correctly formatted string"
        "${f1} - ${f2}" == str
    }
}
但是,当我运行此命令时,会出现以下故障/错误:

Condition not satisfied:

"${f1} - ${f2}" == str
  |        |     |  |
  fizzy    buzzy |  null - null
                 false
                 <omitting details here for brevity>

Expected :null - null

Actual   :fizzy - buzzy

知道我哪里出错了吗?

如果您将规格更改为:

class CarSpec extends Specification {
    def "toString() generates a correct string"() {
        given: "a Car with some mocked dependencies"
        String f1 = 'fizzy'
        String f2 = 'buzzy'
        Fizz fizz1 = Mock()
        Fizz fizz2 = Mock()

        Car car = new Car(1L, fizz1, fizz2)

        when: "we call toString()"
        String str = car.toString()

        then: "we get a correctly formatted string + getProperty('name') is called once on each Mock"
        "$f1 - $f2" == str

        1 * fizz1.getProperty('name') >> f1
        1 * fizz2.getProperty('name') >> f2
    }
}

因此,您在then块中定义了交互,那么它应该可以正常工作…

如果您将规范更改为:

class CarSpec extends Specification {
    def "toString() generates a correct string"() {
        given: "a Car with some mocked dependencies"
        String f1 = 'fizzy'
        String f2 = 'buzzy'
        Fizz fizz1 = Mock()
        Fizz fizz2 = Mock()

        Car car = new Car(1L, fizz1, fizz2)

        when: "we call toString()"
        String str = car.toString()

        then: "we get a correctly formatted string + getProperty('name') is called once on each Mock"
        "$f1 - $f2" == str

        1 * fizz1.getProperty('name') >> f1
        1 * fizz2.getProperty('name') >> f2
    }
}

因此,您在then块中定义了交互,那么它应该可以正常工作…

从我们对@smeeb的另一个不同的讨论中,我对此进行了更多的研究,因为我非常困惑为什么这不起作用

我创建了自己的测试

class SomeTest extends Specification {

    static class Driver {

        String getName(Superclass superclass) {
            return superclass.name
        }
    }

    static abstract class Superclass {
        String name
    }

    static class Subclass extends Superclass {
    }


    def 'test'() {
        given:
        def driver = new Driver()
        def subclass = Mock(Subclass)

        subclass.name >> 'test'

        expect:
        driver.getName(subclass) == 'test'
    }
}
它失败的原因与@smeeb看到的问题相同

driver.getName(subclass) == 'test'
|      |       |         |
|      null    |         false
|              Mock for type 'Subclass' named 'subclass'
我尝试更改了一些不同的内容,发现当我从超类中删除抽象修饰符或将return Superclass.name更改为return Superclass.getName时,测试开始工作

在Groovy级别上,使用自动生成的访问器从抽象超类继承公共字段之间似乎存在一种奇怪的交互

因此,在您的情况下,要么从FooBaz中删除抽象修饰符,要么将代码更改为:

@Override
String toString() {
    "${fizz1.getName()} - ${fizz2.getName()}"
}

从我们对@smeeb的另一个讨论中,我对这一点进行了更多的探讨,因为我非常困惑为什么这不起作用

我创建了自己的测试

class SomeTest extends Specification {

    static class Driver {

        String getName(Superclass superclass) {
            return superclass.name
        }
    }

    static abstract class Superclass {
        String name
    }

    static class Subclass extends Superclass {
    }


    def 'test'() {
        given:
        def driver = new Driver()
        def subclass = Mock(Subclass)

        subclass.name >> 'test'

        expect:
        driver.getName(subclass) == 'test'
    }
}
它失败的原因与@smeeb看到的问题相同

driver.getName(subclass) == 'test'
|      |       |         |
|      null    |         false
|              Mock for type 'Subclass' named 'subclass'
我尝试更改了一些不同的内容,发现当我从超类中删除抽象修饰符或将return Superclass.name更改为return Superclass.getName时,测试开始工作

在Groovy级别上,使用自动生成的访问器从抽象超类继承公共字段之间似乎存在一种奇怪的交互

因此,在您的情况下,要么从FooBaz中删除抽象修饰符,要么将代码更改为:

@Override
String toString() {
    "${fizz1.getName()} - ${fizz2.getName()}"
}

你没有提供汽水课。我已经用一个简单的Fizz界面测试了你的例子,它工作了谢谢@JérémieB+1-如果你认为有必要,我会包括Fizz,但是既然它们是作为模拟注入的,为什么它们很重要呢?这可能是你如何模拟Fizz,这是你的错误的原因。使用Fizz{String getName},您的示例可以正常工作。所以它与@tupleconstructor没有关系不是答案,但是你不需要Fizz-fizz1=MockFizz,只要Fizz-fizz1=Mock-will-down你为什么要模拟一个类,而不是创建一个新实例?但是,对于groovy 2.4.5和cglib/objenesis,如果您没有提供Fizz类,这个示例就可以运行了。我已经用一个简单的Fizz界面测试了你的例子,它工作了谢谢@JérémieB+1-如果你认为有必要,我会包括Fizz,但是既然它们是作为模拟注入的,为什么它们很重要呢?这可能是你如何模拟Fizz,这是你的错误的原因。使用Fizz{String getName},您的示例可以正常工作。所以它与@tupleconstructor没有关系不是答案,但是你不需要Fizz-fizz1=MockFizz,只要Fizz-fizz1=Mock-will-down你为什么要模拟一个类,而不是创建一个新实例?然而,对于groovy 2.4.5和cglib/objenesis,这个例子适用于tooThanks@tim_yates+1-我会尝试一下,但对我来说,这似乎有点倒退+违反直觉!你是说你在已经用它们做了断言之后把你的模拟连接起来了?!?在Java/mockitoland中,您创建您的mock,连接/配置它们以返回/表现您想要的方式,然后使用它们。听起来像是在斯波克土地,你创建你的模拟,使用它们,然后连接/配置它们?!?对正当“但这个例子只是验证,”蒂姆·耶茨说。您在上面建议的是验证+配置。我不是说你错了,我是说斯波克很奇怪而且违反直觉。另外,getProperty方法在该链接中没有出现,所以它不仅仅出现在文档中,这就是groovy。obj.prop首先调用getProperty,所以这就是在你的mock上被调用的,因为你的mock没有实现getProperty,它不会比这更进一步。如果obj.prop成为getProperty,那么我想真正的问题是:obj.prop也会成为名称吗,特别是当我使用双猫王>>时。谢谢@tim_yates+1-我会尝试一下,但对我来说,这似乎有点倒退+违反直觉!你是说你在已经用它们做了断言之后把你的模拟连接起来了?!?在Java/mockitoland中,您创建您的mock,连接/配置它们以返回/表现您想要的方式,然后使用它们。听起来像是在斯波克土地,你创建你的模拟,使用它们,然后连接/配置它们?!?对正当“但这个例子只是验证,”蒂姆·耶茨说。你在干什么
上面的手势是验证+配置。我不是说你错了,我是说斯波克很奇怪而且违反直觉。另外,getProperty方法在该链接中没有出现,所以它不仅仅出现在文档中,这就是groovy。obj.prop首先调用getProperty,所以这就是在你的mock上被调用的,因为你的mock没有实现getProperty,它不会进一步如果obj.prop变成getProperty,那么我想真正的问题是:obj.prop也变成name了吗,特别是当我使用双猫王>>的时候。所以基本上Groovy和Spock对彼此来说太花哨了。我喜欢Groovy和所有东西,但是如果我不错过我的类型安全性,gd dmn!我也有同感。我在任何地方都找不到这一特定案例的文档记录,但在过去几年中,我遇到了其他Groovy gotchas,我花了几个小时才在它们的追踪器上找到了一个无法修复的bug标签。其中一个特别涉及从子类调用抽象超类方法中的闭包,导致闭包无法访问超类中的私有字段和方法。Java8Lambda没有这个问题。如果Scala和Groovy有孩子的话,我正在与Kotlin一起获得经验,这样我就可以把它推荐给我的下一个工作任务。所以Groovy和Spock基本上对彼此来说太花哨了。我喜欢Groovy和所有东西,但是如果我不错过我的类型安全性,gd dmn!我也有同感。我在任何地方都找不到这一特定案例的文档记录,但在过去几年中,我遇到了其他Groovy gotchas,我花了几个小时才在它们的追踪器上找到了一个无法修复的bug标签。其中一个特别涉及从子类调用抽象超类方法中的闭包,导致闭包无法访问超类中的私有字段和方法。Java8Lambda没有这个问题。如果Scala和Groovy有孩子的话,我正在学习Kotlin的经验,这样我就可以把它推荐给我的下一个工作任务。