unit-testing - 无法获取查询以进行渴望的一对多关联加载

标签 unit-testing grails eager-loading

我正在使用Grails v3.3.9。

我无法查询到急切加载一对多关联的查询。尝试了各种方式。

这是相对于在单元测试中尝试使用它而发布的(在运行时在应用程序上也失败)。

场景:我有两个域类,一个叫做'OrgRoleInstance',它具有Collection<Site>站点,另一个是static hasMany =[sites:Site],另一个叫'Site',其具有static belongsTo = [org:OrgRoleInstance]

这是从组织到站点的双向一对多。

我使用新的DomainUnitTest特性创建一个新的单元测试。在测试设置中,我创建了三个组织,每个组织添加一个站点,然后向第三个组织(组织“C”)添加了最后一个站点。安装程序可以正常运行,并且所有实例均保留下来。

在where查询测试中,我寻找一个具有2个站点的组织(即组织“C”)。

当我在调试中运行测试时,where查询返回一个包含组织“C”的大小为1的数组,因此where子句中的测试可以看到sites集合不是null-到目前为止很好。

我也直接执行Site.get(4)来获取最后一个站点。我进行了断言检查,以确保sites.org ref与where查询返回的实例相同。这是事实,并且过去了。

但是,当您查看orgs [0]条目时,sites集合为null。通过访问查询返回的空网站属性,该简单的println失败。

class OrgRoleInstanceSpec extends Specification implements DomainUnitTest<OrgRoleInstance> {


    def setup() {
        OrgRoleInstance

        List<OrgRoleInstance> orgs = []
        ["A","B","C"].each {
            OrgRoleInstance org = new OrgRoleInstance(name:it, role:OrgRoleInstance.OrgRoleType.Customer)
            org.addToSites(new Site( name: "$it's Head Office", status:"open", org:org))
            orgs << org

        }
        orgs[2].addToSites (new Site( name: "${orgs[2].name}'s Branch Office", status:"open", org:orgs[2]))
        OrgRoleInstance.saveAll(orgs)
        assert OrgRoleInstance.count() == 3
        println "# of sites : " + Site.count()
        assert Site.count() == 4
        assert Site.get(2).org.id == orgs[1].id
    }


    void "where query and individual get " () {
        given :

        def orgs = OrgRoleInstance.where {
            sites.size() == 2
        }.list(fetch:[sites:"eager"])

             def org = OrgRoleInstance.get(2)
            List orgSites = org.sites

            def branch = Site.get(4)

            assert branch.org.is (orgs[0]) //assert is true

            println orgs[0].sites[1].name  //orgs[0].sites is null !


        expect:
        orgs.size() == 1

    }

}

我已经用Criteria,基本的findAll(fetch:[sites:"eager")等尝试过

但是,我尝试这样做,我无法获得查询以返回渴望填充的网站集合。

我想在查询中执行此操作,而不是通过OrgeRoleInstance域类中的映射子句进行操作

更新/观察

我启用了SQL日志记录并启动了grails控制台。然后,我键入以下脚本(使用我的 bootstrap 数据)。

手动输入控制台脚本
import com.softwood.domain.*

def orgs = OrgRoleInstance.where {
 id == 4
 sites{}
 }.list() /* (fetch:[sites:"eager"]) */

println orgs[0].sites

运行时会输出以下内容-基本上似乎确实是在进行急切的选择-然后我使用内部联接获得orgs.sites结果以及填充的条目:
groovy> import com.softwood.domain.* 
groovy> def orgs = OrgRoleInstance.where { 
groovy>  id == 4 
groovy>  sites{} 
groovy>  }.list() /* (fetch:[sites:"eager"]) */ 
groovy> println orgs[0].sites 

2019-01-23 14:02:00.923 DEBUG --- [      Thread-18] org.hibernate.SQL                        : 
    select
        this_.id as id1_16_1_,
        this_.version as version2_16_1_,
        this_.role as role3_16_1_,
        this_.name as name4_16_1_,
        sites_alia1_.id as id1_21_0_,
        sites_alia1_.version as version2_21_0_,
        sites_alia1_.org_id as org_id3_21_0_,
        sites_alia1_.name as name4_21_0_,
        sites_alia1_.status as status5_21_0_ 
    from
        org_role_instance this_ 
    inner join
        site sites_alia1_ 
            on this_.id=sites_alia1_.org_id 
    where
        this_.id=?
2019-01-23 14:02:00.923 TRACE --- [      Thread-18] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
2019-01-23 14:02:00.923 DEBUG --- [      Thread-18] org.hibernate.SQL                        : 
    select
        sites0_.org_id as org_id3_21_0_,
        sites0_.id as id1_21_0_,
        sites0_.id as id1_21_1_,
        sites0_.version as version2_21_1_,
        sites0_.org_id as org_id3_21_1_,
        sites0_.name as name4_21_1_,
        sites0_.status as status5_21_1_ 
    from
        site sites0_ 
    where
        sites0_.org_id=?
2019-01-23 14:02:00.923 TRACE --- [      Thread-18] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [4]
[Site:(name : 1 Barkley Square) belonging to org: com.softwood.domain.OrgRoleInstance : 4, Site:(name : 10 South Close) belonging to org: com.softwood.domain.OrgRoleInstance : 4]

那么,为什么这在单元测试中不起作用?

另一个更新

我回到grails控制台并设法使此条件查询生效。要点1-您必须导入org.hibernate.FetchMode,然后withCriteria闭包中的根级别的fetchMode函数现在可以使用。最后,只需对集合执行空关闭以强制执行急切查询。
import com.softwood.domain.*
import org.hibernate.FetchMode

def orgs = OrgRoleInstance.withCriteria {
            fetchMode ("sites", FetchMode.SELECT)


            sites{}
        }

println orgs[0].sites

但是,这在单元测试中不起作用。像这样的单元测试中的相同查询
void "criteria query " () {
    given:

    OrgRoleInstance org

    org = OrgRoleInstance.withCriteria (uniqueResult: true) {
        fetchMode ("sites", FetchMode.SELECT)

        sites{}
    }

    expect:
    org.id == 3
    org.sites.size() == 2

}

失败并显示此错误
groovy.lang.MissingMethodException: No signature of method: grails.gorm.CriteriaBuilder.fetchMode() is applicable for argument types: (java.lang.String, org.hibernate.FetchMode) values: [sites, SELECT]

因此,我怀疑单元测试条件查询或使用新的grails DomainUnitTest<T>特性的查询不支持联接/渴望查询等。

可能您被迫将测试与真实数据库进行集成以测试跨表的查询。如果有人可以断言,新的单元测试特征不适用于可能对我有帮助的联接/急切查询。

最佳答案

该调查的输出是,您不能将单元测试用于试图使用新的单元测试特征联接表的域模型查询。

如果要查询测试,请作为集成测试来进行。

这次我将查询重新编码为集成测试时,允许在spock setup()方法中的任何数据之前加载引导数据,然后查询开始工作,并且包括对数据的急切查询。

要记住的一件事是setup()方法不会进行回滚(这样数据就可以在整个测试中保留),因此如果数据库没有被删除,则应该在最后进行手动清除。

因此,这没有被编码为集成测试,并且似乎可以正常工作!

package com.softwood.domain

import grails.testing.mixin.integration.Integration
import grails.transaction.*
import org.hibernate.FetchMode
import org.hibernate.LazyInitializationException
import spock.lang.Shared
import spock.lang.Specification

@Integration
@Rollback
class OrgRoleInstanceIntegSpecSpec extends Specification {

    //operates on separate transaction thats not rolled back
    @Shared
    List<OrgRoleInstance> orgs = []

    @Shared
    List<OrgRoleInstance> sites = []

    @Shared
    NetworkDomain netDomain

    @Shared
    def bootstrapPreExistingOrgsCount, bootstrapPreExistingSitesCount

    //runs in transaction thats not rolled back for each test
    def setup() {
        def site

        bootstrapPreExistingOrgsCount = OrgRoleInstance.count()
        bootstrapPreExistingSitesCount = Site.count()
        //println "pre exist orgs:$boostrapPreExistingOrgsCount + pre exist sites: $boostrapPreExistingSitesCount"
        assert bootstrapPreExistingOrgsCount == 4
        assert bootstrapPreExistingSitesCount == 2

        ["A","B","C"].each {
            OrgRoleInstance org = new OrgRoleInstance(name:"test$it", role:OrgRoleInstance.OrgRoleType.Customer)
            org.addToSites(site = new Site( name: "test$it's Head Office", status:"open", org:org))
            orgs << org
            sites << site

        }
        orgs[2].addToSites (site = new Site( name: "${orgs[2].name}'s Branch Office", status:"open", org:orgs[2]))
        orgs[2].addToDomains(netDomain = new NetworkDomain (name:"corporate WAN", customer:[orgs[2]]))
        sites << site

        OrgRoleInstance.saveAll(orgs)
        assert orgs.size() == 3
        assert sites.size() ==4
        assert OrgRoleInstance.count() == 3 + bootstrapPreExistingOrgsCount
        assert Site.count() == 4 + bootstrapPreExistingSitesCount
        assert Site.get(4).org.id == orgs[1].id
        println "setup integration test data"

    }

    //manual cleanup of integration test data
    def cleanup() {

        orgs.each {OrgRoleInstance org ->
            org.sites.each {it.delete()}
            org.delete(flush:true)
            assert OrgRoleInstance.exists(org.id) == false
        }
        println "deleted integration test data"
    }

    void "Orgs list with eager fetch query"() {
        given :

        def orgs = OrgRoleInstance.list(fetch:[sites:"eager"])
        println "org ${orgs[6].name} sites : " + orgs[5].sites

        println "test site #2  has org as : " + (Site.list())[3].org

        expect :
        Site.count() == 4 + bootstrapPreExistingSitesCount
        orgs.size() == 3 + bootstrapPreExistingOrgsCount
        orgs[5].getName() == "testB"
        orgs[5].sites.size() == 1

    }

    void "orgs where query triggering eager site get"() {
        given :

        //where clause returns instance of DetachedCriteria, so have to trigger with a list/get etc
        def orgs = OrgRoleInstance.where {
            name =~ "%testB%" &&
                    sites{}
        }.list()

        expect :
        orgs.size() == 1
        orgs[0].name == "testB"
        orgs[0].sites.size() == 1

    }

    void "withCriteria query with eager site fetch (two selects)  " () {
        given:

        OrgRoleInstance org

        //with criteria runs the query for you unlike createCriteria() which returns  a detachedCriteria
        org = OrgRoleInstance.withCriteria (uniqueResult: true) {
            fetchMode ("sites", FetchMode.SELECT)
            idEq(7L)  //internally wont cast Integer to long, so set it explicitly
            sites{}
        }

        /*def orgs = OrgRoleInstance.withCriteria {
            //setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
            eq 'name', "B"
            //fetchMode 'sites', FetchMode.SELECT
            sites{}
        }*/

        expect:
        org.id == 7
        org.sites.size() == 2

    }

    void "detached criteria (with distinct) with eager fetch " () {
        given:


        def orgs = OrgRoleInstance.createCriteria().listDistinct {
            //fetchMode 'sites', FetchMode.SELECT
            join 'sites'
            sites {
                org {
                    eq 'id', 6L
                }
            }
        }

        def site = orgs[0].sites[0]

        expect:
        orgs.size() == 1
        orgs[0].sites.size() == 1
        site.name == "testB's Head Office"

    }

    void "where query on id only without list (fetch eager) " () {
        given :

        def orgs = OrgRoleInstance.where {
            id == 7L
        }.list ()

        def branch = orgs[0].sites[0]

        when:

        println "branch "+ branch.name  //try and access branch

        then:
        /*
        -- seems to do an eager fetch on sites+domains, even though i didnt ask it to
         not quite sure why - separate exploration round that i think
         */
        //LazyInitializationException ex = thrown()
        orgs.size() == 1
        orgs[0].sites.size() == 2
        orgs[0].domains.size() == 1

    }

}

我希望这可以使其他人避免您的测试为什么不起作用。

还要注意,运行grails控制台将启动控制台脚本应用程序,但会使用它启动一个gorm构建-因此,可以明智地使用include域包-您可以针对gorm启动时加载的所有 bootstrap 数据交互地测试一些测试查询。

集成测试费时费力且运行时间昂贵。

如果可以增强单元测试特征以支持查询测试,那就太好了(而且很聪明)。

关于unit-testing - 无法获取查询以进行渴望的一对多关联加载,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54325664/

相关文章:

asp.net-mvc - ASP.Net MVC 中 HttpContext 和可测试 Controller 的最佳实践

grails - grails标准 'has at least one'

grails - ils。域类。 1:米

grails - 我可以使用 GrailsS​​wingConsole 应用程序来测试服务吗?

ruby-on-rails - Rails 3 Eager Loading with conditions - 如何访问急切加载的数据?

python - 单元测试 Flask Babel 翻译

javascript - 如何通过单元测试检查字符串是否恰好出现 1 次?

Android:jUnit 测试 Android 库

php - Laravel:模型中的动态关系

python - 完成对象及其关系并避免在 sqlalchemy 中进行不必要的查询