如何在Spock测试中模拟模拟类的局部变量?

时间:2014-08-26 18:49:47

标签: unit-testing variables grails mocking spock

我们说我在服务中有以下方法:

private void deleteItems(List<Item> itemsToDelete) {
    def sql = new Sql(dataSource)
    itemsToDelete?.each { Item item ->
        sql.execute("DELETE FROM owner_item WHERE item_id = ${item.id}")
        item.delete(flush: true, failOnError: true)
        flushDatabaseSession();
    }
}

如何在ItemServiceSpec中为此方法创建测试?当我尝试它时,我得到一个DataSource &#34;必须在sql上指定一个非空的Connection&#34; 错误或 nullPointerException

这是我现有的测试。

@TestFor(ItemService)
@Mock([Item])
@Build([Item])
class SubjectServiceSpec extends Specification {

...

    def "delete items"() {
        given:
        Item item1 = Item.build().save(flush: true)
        Item item2 = Item.build().save(flush: true)
        Item.count() == 2
        DataSource mockDataSource = Mock()
        service.dataSource = mockDataSource
        1 * deleteItems

        when:
        service.deleteItems([item1, item2])

        then:
        Item.count() == 0
    }
}

1 个答案:

答案 0 :(得分:1)

您在这里尝试做的是模拟依赖项(DataSource)的依赖项(Sql)。这通常会导致您无法100%了解SqlDataSource对象的交互方式。如果Sql更改版本更新中与Datasource的私密互动,则必须处理此情况。

不是模拟依赖项的依赖项,而是直接使用Sql类。为此,sql必须是某种明确的依赖关系,您可以通过DI或方法参数接收。在这种情况下,您可以像这样模拟execute调用(选择Expando-Mock的方式,但您也可以使用Map或来自Spock的模拟东西):

given:
def sqlMock = new Expando()
sqlMock.execute = { return 'what ever you want or nothing, because you mock a delete operation' }
service.sql = sqlMock

when:
service.deleteItems([item1, item2])

then:
assertItemsAreDeletedAndTheOwnerAsWell()

考虑整个测试用例,我认为有两个主要问题。

第一个是,当你问自己,通过嘲笑整个sql的东西,你真正得到了什么样的确定性。在这种情况下,您在这里唯一要做的就是与db进行交互。当你嘲笑这件事时,你就无法测试了。没有多少有条件的东西或任何应由单元测试备份的东西。因此,我建议只为此测试用例编写集成规范,在这种测试用例中,您可以使用H2DB进行测试。

第二件事是,你实际上根本不需要Sql操作。如果项目被删除,您可以通过自动和透明删除项目所有者的方式配置GORM和Hibernate。为此,请查看GORM中的文档(尤其是cascade part)或直接在Hibernate docs中查看文档。

总结一下:使用cascade: 'delete'和正确的集成测试,您可以获得更多的确定性和更少的样板代码。