Grails 的正确 Spock 语法是什么?

What is the correct Spock syntax for Grails?

我有一个 Grails 2.5.0 应用程序 运行ning 和这个测试:

package moduleextractor

import grails.test.mixin.TestFor
import spock.lang.Specification

/**
 * See the API for {@link grails.test.mixin.web.ControllerUnitTestMixin} for usage instructions
 */
@TestFor(ExtractorController)
class ExtractorControllerSpec extends Specification {

    def moduleDataService
    def mockFile

    def setup() {
        moduleDataService = Mock(ModuleDataService)
        mockFile = Mock(File)
    }

    def cleanup() {
    }

    void "calls the moduleDataService"() {
        given: 'a term is passed'
            params.termCode = termCode
        when: 'the getModuleData action is called'
            controller.getModuleData()
        then: 'the service is called 1 time'
            1 * moduleDataService.getDataFile(termCode, 'json') >> mockFile
        where:
            termCode = "201415"
    }
}

如果我 运行 grails test-app unit:spock 我明白了:

| Tests PASSED - view reports in /home/foo/Projects/moduleExtractor/target/test-reports

我不明白为什么它会看到 2 个测试。我没有在我的 BuildConfig 文件中包含 spock,因为它已经包含在 Grails 2.5.0 中。此外,测试不应该通过,因为我还没有服务。为什么会通过?

另外,当我 运行 这个 grails test-app ExtractorController 我得到另一个结果:

| Running 2 unit tests...
| Running 2 unit tests... 1 of 2
| Failure:  calls the moduleDataService(moduleextractor.ExtractorControllerSpec)
|  Too few invocations for:
1 * moduleDataService.getDataFile(termCode, 'json') >> mockFile   (0 invocations)
Unmatched invocations (ordered by similarity):
None
    at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:78)
    at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:76)
    at moduleextractor.ExtractorControllerSpec.calls the moduleDataService(ExtractorControllerSpec.groovy:27)
| Completed 1 unit test, 1 failed in 0m 3s
| Tests FAILED  - view reports in /home/foo/Projects/moduleExtractor/target/test-reports
| Error Forked Grails VM exited with error

如果我 运行 grails test-app unit: 我得到:

| Running 4 unit tests...
| Running 4 unit tests... 1 of 4
| Failure:  calls the moduleDataService(moduleextractor.ExtractorControllerSpec)
|  Too few invocations for:
1 * moduleDataService.getDataFile(termCode, 'json') >> mockFile   (0 invocations)
Unmatched invocations (ordered by similarity):
None
    at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:78)
    at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:76)
    at moduleextractor.ExtractorControllerSpec.calls the moduleDataService(ExtractorControllerSpec.groovy:27)
| Completed 1 unit test, 1 failed in 0m 3s
| Tests FAILED  - view reports in /home/foo/Projects/moduleExtractor/target/test-reports
| Error Forked Grails VM exited with error

首先有人能告诉我 运行 spock 测试的正确语法是什么吗?

此外,在命令中使用 unitunit: 以及 unit:spock 有什么区别?

(由于 Spock 随 Grails 2.5.0 一起提供,因此无论如何它都会 运行 spocks 测试。)

什么是正确的语法?为什么它看到 2 个测试而不是 1 个?

不要在意测试的数量。这对我来说从来都不是问题。您可以随时检查报告 HTML 文件以确切了解 运行.

我总是运行我的测试

grails test-app

grails test-app ExtractorController

您遇到的错误意味着您将测试编码为期望 moduleDataService.getDataFile() 以参数 null 和 'json' 当调用 controller.getModuleData() 时。但是,moduleDataService.getDataFile() 从未被调用,因此测试失败。

Spock 需要一些时间来适应。我建议查看 Grails 文档中的示例并阅读 Spock Framework Reference.

第一个问题:对于 'grails test-app unit:spock',您是否查看了结果以查看其表示通过的测试? CLI 的测试计数可能是错误的,请检查您的结果以查看实际 运行(如果实际上没有测试 运行,则没有失败)。

您的测试方法不是以 'test' 开头,也没有 @Test 注释,因此 'void "calls the moduleDataService"' 不会被视为 spock 测试用例(我相信这是原因)。

当您 运行 'grails test-app ExtractorController' 时,您没有指定它必须是 spock 测试,因此 grails 测试会找到并执行 'calls the moduleDataService' 测试方法。

由于spock是事实上的测试框架,你可以直接使用: grails test-app -unit

第二个问题:

@TestFor 创建您的控制器,但如果您要 运行 进行单元测试,那么通常的 grails 魔术就不会发生。您的控制器代码是独立执行的。如果您的 ExtractorController 通常注入了 moduleDataService,则您必须注意这一点。

我在 grails 2.4.3 工作,这里是我对你的测试的解释(肯定需要调整,因为我在这个例子中推断了很多):

import grails.test.mixin.TestFor
import grails.test.mixin.Mock
import spock.lang.specification
import some.pkg.ModuleDataService // if necessary
import some.pkg.File // if necessary

@TestFor(ExtractorController)
@Mock([ModuleDataService, File])
class ExtractorControllerSpec extends Specification

    def "test callsModuleDataService once for a termCode"() {

        setup:
        def mockFile = mockFor(File)
        def mockService = mockFor(ModuleDataService, true) // loose mock
        // in this mockService, we expect getDataFile to be called
        // just once, with two parameters, and it'll return a mocked
        // file
        mockService.demand.getDataFile(1) { String termCode, String fmt ->
            return mockFile.createMock()
        }
        controller.moduleDataService = mockService.createMock()

        when:
        controller.params.termCode = "201415"
        controller.getModuleData()

        then:
        response.status == 200 // all good?
    }
}

最后一个问题:那是Banner字词代码吗? (只是好奇)