如何在单元测试中为特定用例设置LaunchDarkly Flag值
问题描述
我正尝试对一个受LaunchDarkly Flag部分控制的函数进行单元测试,希望除一个测试用例外,其余所有用例的Flag值均设为true。为实现这一点,我尝试为该例外用例的上下文设置特定属性,以便LaunchDarkly evaluator能够识别,但始终找不到正确的上下文键、上下文类型(context kind)和属性配置方式。
以下是我的代码实现:
测试文件代码
import ( "github.com/launchdarkly/go-server-sdk/v7/ldcomponents" "github.com/launchdarkly/go-server-sdk/v7/testhelpers/ldtestdata" ld "github.com/launchdarkly/go-server-sdk/v7" ofld "github.com/open-feature/go-sdk-contrib/providers/launchdarkly/pkg" "github.com/open-feature/go-sdk/openfeature" ) func NewTestFlaggingClient() (*ldtestdata.TestDataSource, *ld.LDClient) { td := ldtestdata.DataSource() var ldConfig ld.Config ldConfig.ApplicationInfo.ApplicationID = "testing" ldConfig.DataSource = td ldConfig.Events = ldcomponents.NoEvents() ldClient, err := ld.MakeCustomClient("fake-sdk-key", ldConfig, 5*time.Second) if err != nil { return nil, err } if err := openfeature.SetProvider(ofld.NewProvider(ldClient)); err != nil { return nil, err } return td, ldClient } func Test_myMethod(t *testing.T) { // ... disabledCtx := openfeature.NewEvaluationContext("user", map[string]interface{}{ "disable-flag": true }) td, flagger := flags.NewTestFlaggingClient() // 我试图创建一个始终为true的Flag // 仅在遇到上述上下文时为false td.Update(td.Flag("method-enabled"). BooleanFlag(). VariationForKey("user", "disable-flag", false). FallthroughVariation(true), ) t.Run("Skip if flag is not set", func(t *testing.T) { // 将disabledCtx添加到父上下文 ctx := openfeature.WithTransactionContext(ctx, disabledCtx) err := myMethod(ctx) // ... }) }
目标函数代码
// myMethod implementation func myMethod(ctx context.Context) error { // Flag评估始终会接收此匿名上下文及父上下文 anonCtx := openfeature.NewEvaluationContext("anonymous", map[string]any{ "kind": "user", "anonymous": true, }) if !p.clients.Flags.Boolean(ctx, "mm-return-processor-enabled", false, anonCtx) { return nil } }
调试openfeature和launchdarkly包后仍未找到解决办法。
解决方案
核心问题在于混淆了OpenFeature上下文与LaunchDarkly测试数据源的规则匹配逻辑,同时myMethod中生成的匿名上下文会干扰父上下文属性传递,具体修正步骤如下:
1. 修正Flag规则的匹配逻辑
VariationForKey仅用于匹配特定用户ID,要基于上下文属性匹配,需使用IfMatch条件规则,同时确保Flag名称与业务代码中一致:
td.Update(td.Flag("mm-return-processor-enabled") // 与myMethod中调用的Flag名完全一致 .BooleanFlag() .IfMatch("disable-flag", true) // 匹配上下文属性disable-flag=true的情况 .ThenVariation(false) .FallthroughVariation(true), // 默认返回true )
2. 修正上下文属性合并逻辑
myMethod中传入的anonCtx会作为评估的主上下文,父上下文属性不会自动继承,需手动合并:
func myMethod(ctx context.Context) error { // 从context中提取父OpenFeature上下文 parentEvalCtx := openfeature.FromContext(ctx) parentAttrs := parentEvalCtx.Attributes() // 创建匿名上下文并合并父属性 anonAttrs := map[string]any{ "kind": "user", "anonymous": true, } // 将父上下文的属性合并到匿名上下文中 for k, v := range parentAttrs { anonAttrs[k] = v } anonCtx := openfeature.NewEvaluationContext("anonymous", anonAttrs) if !p.clients.Flags.Boolean(ctx, "mm-return-processor-enabled", false, anonCtx) { return nil } // 业务逻辑... }
3. 修正测试用例的上下文传递
确保测试上下文被正确存入context.Context中:
func Test_myMethod(t *testing.T) { // 创建带禁用属性的测试上下文 disabledAttrs := map[string]interface{}{"disable-flag": true} disabledEvalCtx := openfeature.NewEvaluationContext("test-user", disabledAttrs) td, _ := flags.NewTestFlaggingClient() // 配置Flag规则 td.Update(td.Flag("mm-return-processor-enabled") .BooleanFlag() .IfMatch("disable-flag", true) .ThenVariation(false) .FallthroughVariation(true), ) t.Run("Skip if flag is disabled", func(t *testing.T) { // 将OpenFeature上下文存入context.Context ctx := openfeature.NewContext(disabledEvalCtx) err := myMethod(ctx) // 断言:预期方法直接返回nil // assert.NoError(t, err) }) t.Run("Execute full logic when flag is enabled", func(t *testing.T) { // 使用不带禁用属性的上下文 enabledEvalCtx := openfeature.NewEvaluationContext("test-user-2", nil) ctx := openfeature.NewContext(enabledEvalCtx) err := myMethod(ctx) // 断言:预期方法执行完整业务逻辑 // ... }) }
关键注意事项
- Flag名称必须完全一致:测试中配置的Flag名要和业务代码中调用的
mm-return-processor-enabled完全匹配,之前测试代码中用的method-enabled是核心错误之一。 - OpenFeature上下文不会自动合并:当调用
Boolean方法传入第二个上下文参数时,它会作为评估的主上下文,父上下文属性不会自动继承,必须手动合并。 - 区分规则匹配方式:
VariationForKey仅匹配用户ID,IfMatch用于匹配上下文属性,不要混淆两者用法。
内容的提问来源于stack exchange,提问作者Gabe
相关产品推荐
相关产品推荐

