从Grails模拟方法抛出异常

5
在Grails的控制器单元测试中(更具体地说是Spock的ControllerSpec),我想要检查被测试方法在协作者抛出异常时的行为。
我使用mockFor工具(来自Spock的UnitSpec或Grails的GrailsUnitTestMixin)来指定测试中对于这种抛出异常方法的需求,像这样:
@TestFor(TestController)
class TestControllerSpec extends Specification {

    def "throwing and exception from a mock method should make the test fail"() {
        setup:
        def serviceMock = mockFor(TestService)
        serviceMock.demand.exceptionThrowingMethod() { throw new Exception() }
        controller.testService = serviceMock.createMock()

        when:
        controller.triggerException()

        then:
        thrown(Exception)
    }
}

因此,在 triggerException 中,我会像这样调用 exceptionThrowingMethod
class TestController {

    def testService

    def triggerException() {
        testService.exceptionThrowingMethod()
    }
}

但是测试失败了,错误信息如下:

期望抛出 java.lang.Exception 异常,但没有抛出异常

我调试了程序,发现并没有抛出异常,调用 exceptionThrowingMethod 竟然返回了一个闭包。即使在方法签名中添加 throws 声明也不起作用。

我以为这与 Spock 有关,但我尝试使用 Grails 的测试 mixin 进行类似的测试,结果也是一样的。这是我的尝试:

@TestFor(TestController)
class TestControllerTests {

    void testException() {
        def serviceMock = mockFor(TestService)
        serviceMock.demand.exceptionThrowingMethod() { throw new Exception() }
        controller.testService = serviceMock.createMock()

        shouldFail(Exception) {
            controller.triggerException()
        }
    }
}

你发现代码中有什么问题吗?

在Grails文档中我没有找到如何强制抛出异常的方法,所以上述代码对我来说是很自然的。

通过谷歌搜索也没有找到相关信息,这让我觉得可能在测试方面我正在尝试做错误的事情。

这不是测试中常见的情况吗?你可以在特定场景下模拟某些方法的确定性行为,然后测试当发生这种情况时被测试方法的预期行为。对我来说,抛出异常看起来像是一个有效的情形。

1个回答

10

似乎将 demand 闭包设置为无参数 (即没有隐式的 it 参数,而是使用显式的 ->) 就能解决问题

serviceMock.demand.exceptionThrowingMethod {-> throw new Exception() }

更新: 您还可以使用Groovy的本地 MockFor 类,似乎不需要这个闭包怪异性:

@TestFor(TestController)
class TestControllerTests {

    void testException() {
        def mock = new MockFor(TestService)
        mock.demand.exceptionThrowingMethod { throw new Exception() }
        controller.testService = mock.proxyInstance()

        shouldFail { controller.triggerException() }
        mock.verify(controller.testService)
    }
}

请注意,如果不使用mock.use,则必须使用mock.verify来验证模拟约束(即exceptionThrowingMethod被调用一次)。

顺便问一下,这在测试中是常见情况吗? :D - epidemian
@doelleri也许"nullary"是一个更常见的同义词,但我认为"niladic"听起来更好! - epidemian
你的回答非常有用。然而,如果你提供的链接题目正确命名的话,我本可以找到它,而不会在 SO 上产生重复。无论如何,它是由一个声望超过 27k 的用户编写的。 - Esteban
@Esteban 嗯,有时候正确描述或命名问题并不容易;基本上是因为你可能不知道哪里出了问题。我链接的那个问题的正确标题是什么?毕竟,它涉及到闭包和mockFor方法,有点奇怪。如果你觉得现在你的问题已经多余了,不要这样想;我认为在这里预计会有一些冗余,因为相同的问题可能会出现在不同的情境中。你的问题很可能会帮助以后遇到这个问题的人。不过,你可以考虑在标题中添加类似“模拟方法返回闭包”的内容 :) - epidemian

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接