You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 14:17:41