Reputation: 1296
I'm trying to unit test a function that is partly controlled by a LaunchDarkly flag. My wish is for the flag to be set to true
for every test case but one.
To achieve that I'm trying to set specific attributes in the context for the false
test case, that can be then evaluated by the LaunchDarkly evaluator, but I can't seem to find the right setup of context keys, context kind, and attributes to make it work.
Here's what I have:
// Test file
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()
// Here I'm trying to create a flag that will always be true
// except when I see the context above
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) {
// add the disabledCtx to the parent context
ctx := openfeature.WithTransactionContext(ctx, disabledCtx)
err := myMethod(ctx)
// ...
})
}
// myMethod implementation
func myMethod(ctx context.Context) error {
// the Flag evaluation always receives this anonymous context, alongside the parent context
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
}
}
I can't really find a working example anywhere, and I tried debugging the openfeature
and laumchdarkly
packages to see what's breaking, but I've found no way out yet.
Upvotes: 2
Views: 130
Reputation: 4618
I'm not exactly sure what you are trying to achieve here, but with
td.Update(td.Flag("method-enabled").
VariationForUser("user1", false).
FallthroughVariation(true),
)
you create a flag method-enabled
that is false
for user1
and true
for everybody else. VariationForUser
is a shorthand for VariationForKey("user", ...
, which is what you are using. I'm unsure if you really meant a user disable-flag
, I go with user1
here.
Assuming your method under test:
package main
import (
"context"
"github.com/open-feature/go-sdk/openfeature"
)
func MyMethod(ctx context.Context) (bool, error) {
of := openfeature.GetApiInstance()
client := of.GetClient()
val, err := client.BooleanValue(ctx, "method-enabled", false, openfeature.EvaluationContext{})
if err != nil {
return false, err
}
return val, nil
}
This test will pass:
package main
import (
"context"
"testing"
"time"
ld "github.com/launchdarkly/go-server-sdk/v7"
"github.com/launchdarkly/go-server-sdk/v7/interfaces"
"github.com/launchdarkly/go-server-sdk/v7/ldcomponents"
"github.com/launchdarkly/go-server-sdk/v7/testhelpers/ldtestdata"
ofld "github.com/open-feature/go-sdk-contrib/providers/launchdarkly/pkg"
"github.com/open-feature/go-sdk/openfeature"
)
func NewTestFlaggingClient() (*ldtestdata.TestDataSource, error) {
td := ldtestdata.DataSource()
ldConfig := ld.Config{
ApplicationInfo: interfaces.ApplicationInfo{
ApplicationID: "testing",
},
DataSource: td,
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, err
}
func TestMyMethod(t *testing.T) {
td, _ := NewTestFlaggingClient()
td.Update(td.Flag("method-enabled").
VariationForUser("user1", false).
FallthroughVariation(true),
)
enabledCtx := openfeature.NewEvaluationContext("user2", nil)
disabledCtx := openfeature.NewEvaluationContext("user1", nil)
ctx := context.Background()
type args struct {
ctx context.Context
}
tests := []struct {
name string
args args
want bool
wantErr bool
}{
{
name: "enabled",
args: args{openfeature.WithTransactionContext(ctx, enabledCtx)},
want: true,
},
{
name: "disabled",
args: args{openfeature.WithTransactionContext(ctx, disabledCtx)},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := MyMethod(tt.args.ctx)
if (err != nil) != tt.wantErr {
t.Errorf("MyMethod() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("MyMethod() = %v, want %v", got, tt.want)
}
})
}
}
When you want to use attributes, use:
...
import "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
...
td.Update(td.Flag("method-enabled").
IfMatch("disable-flag", ldvalue.Bool(true)).ThenReturn(false).
FallthroughVariation(true),
)
enabledCtx := openfeature.NewEvaluationContext("user", nil)
disabledCtx := openfeature.NewEvaluationContext("user", map[string]interface{}{"disable-flag": true})
...
Also, see this document for some attributes mapped from OpenFeature to LaunchDarkly, like anonymous contexts.
As you can easily verify, with MyMethod
defined as:
func MyMethod(ctx context.Context) (bool, error) {
of := openfeature.GetApiInstance()
client := of.GetClient()
anonCtx := openfeature.NewEvaluationContext("anonymous", map[string]any{
"kind": "user",
"anonymous": true,
})
val, err := client.BooleanValue(ctx, "method-enabled", false, anonCtx)
if err != nil {
return false, err
}
return val, nil
}
the tests pass too. With "mm-return-processor-enabled"
you won't get what you want, since this flag is not defined anywhere else.
Upvotes: 1