Grails多租户Domain Class单元测试遇TenantNotFoundException报错求助
解决Grails多租户领域类单元测试中的TenantNotFoundException问题
这个问题我之前也碰到过,核心原因是单元测试环境里没有初始化多租户上下文:你的自定义租户解析器CustomTenentResolver依赖Spring Security的principal来获取租户ID,但测试时没有登录用户,导致解析器抛出TenantNotFoundException。下面给你几个实用的解决方案:
方案1:手动设置租户上下文(快速简单)
Grails提供了TenantContext类,可以直接在测试中手动设置当前租户ID,不需要修改解析器或配置。
修改你的AccountSpec,在测试前后设置/清理租户上下文:
package crm import grails.testing.gorm.DomainUnitTest import org.grails.datastore.mapping.multitenancy.TenantContext import spock.lang.Specification class AccountSpec extends Specification implements DomainUnitTest<Account> { def setup() { // 设置测试用的固定租户ID TenantContext.setCurrentTenantId('test-tenant-001') } def cleanup() { // 清理租户上下文,避免影响其他测试用例 TenantContext.clear() } void 'test name cannot be null'() { when: domain.mobile = null then: !domain.validate(['mobile']) domain.errors['mobile'].code == 'nullable' } }
方案2:为测试环境配置专属租户解析器
如果你的所有多租户领域类测试都需要默认租户,可以在测试环境中替换自定义解析器,用一个固定返回测试租户的实现。
步骤1:创建测试用租户解析器
package usermanagement import org.grails.datastore.mapping.multitenancy.AllTenantsResolver import org.grails.datastore.mapping.multitenancy.exceptions.TenantNotFoundException class TestTenantResolver implements AllTenantsResolver { @Override Iterable<Serializable> resolveTenantIds() { // 返回测试用的租户ID列表 return ['test-tenant-001'] } @Override Serializable resolveTenantIdentifier() throws TenantNotFoundException { // 固定返回测试租户ID return 'test-tenant-001' } }
步骤2:在测试配置中替换解析器
修改grails-app/conf/application-test.yml,指定测试环境使用这个解析器:
grails: gorm: multiTenancy: tenantResolverClass: usermanagement.TestTenantResolver
这样所有单元测试都会自动使用这个固定租户,不需要在每个测试类里重复设置。
方案3:模拟Spring Security上下文(贴近真实场景)
如果你想模拟真实的登录用户场景,可以用Spock的Mock功能,模拟Spring Security的principal和相关依赖。
修改AccountSpec如下:
package crm import grails.testing.gorm.DomainUnitTest import grails.plugin.springsecurity.SpringSecurityService import spock.lang.Specification import usermanagement.User import usermanagement.Organisation import org.springframework.security.core.userdetails.UserDetails class AccountSpec extends Specification implements DomainUnitTest<Account> { // Mock SpringSecurityService SpringSecurityService springSecurityService = Mock() def setup() { // 模拟一个带有租户信息的用户 def testOrg = new Organisation(namespace: 'test-tenant-001') def testUser = new User(username: 'test-user', organisation: testOrg) // 静态方法Mock:让User.findByUsername返回模拟用户 User.metaClass.static.findByUsername = { String username -> testUser } // 模拟SpringSecurity的principal springSecurityService.principal >> Mock(UserDetails) { getUsername() >> 'test-user' } } void 'test name cannot be null'() { when: domain.mobile = null then: !domain.validate(['mobile']) domain.errors['mobile'].code == 'nullable' } }
这个方案更贴近生产环境的逻辑,但配置稍复杂,适合需要验证租户相关业务规则的测试。
内容的提问来源于stack exchange,提问作者user2253081
相关产品推荐
相关产品推荐

