Repository: Unleash/unleash-client-go
Pull request #194 (2025-04-08 15:34:21)

feat: impression data
https://linear.app/unleash/issue/2-3494/go-sdk-impression-event-support

Implements impression data in our Go SDK, similar to what we already have for other SDKs.

Closes https://github.com/Unleash/unleash-client-go/issues/182


Patch:
diff --git a/README.md b/README.md
index a15ece6..e672844 100644
--- a/README.md
+++ b/README.md
@@ -264,6 +264,31 @@ resolver := func(featureName string) *api.Feature {
 unleash.IsEnabled("someToggle", unleash.WithContext(ctx), unleash.WithResolver(resolver))
 ```
 
+### Impression data
+
+When impression data is enabled on a flag, the SDK will emit impression events during evaluation. You can hook into these with a custom listener to collect insights or integrate with analytics. You can [read more about impression data in Unleash's documentation](https://docs.getunleash.io/reference/impression-data).
+
+```go
+type MyListener struct {
+	unleash.DebugListener
+}
+
+func (l *MyListener) OnImpression(e unleash.ImpressionEvent) {
+	fmt.Printf("Custom Impression: %s = %v (%s)\n", e.FeatureName, e.Enabled, e.EventType)
+	if e.Context != nil {
+		fmt.Printf("Context: userId=%s sessionId=%s\n", e.Context.UserId, e.Context.SessionId)
+	}
+}
+
+func main() {
+	unleash.Initialize(
+		unleash.WithListener(&MyListener{}),
+		unleash.WithAppName("my-app"),
+		unleash.WithUrl("http://unleash.herokuapp.com/api/"),
+	)
+}
+```
+
 ## Development
 
 To override dependency on unleash-client-go github repository to a local development folder (for instance when building a local test-app for the SDK),  
diff --git a/client.go b/client.go
index 7ad7f57..1d22528 100644
--- a/client.go
+++ b/client.go
@@ -51,6 +51,7 @@ type Client struct {
 	errorListener      ErrorListener
 	metricsListener    MetricListener
 	repositoryListener RepositoryListener
+	impressionListener ImpressionListener
 	ready              chan bool
 	onReady            chan struct{}
 	close              chan struct{}
@@ -58,6 +59,7 @@ type Client struct {
 	count              chan metric
 	sent               chan MetricsData
 	registered         chan ClientData
+	impression         chan ImpressionEvent
 	staticContext      *context.Context
 }
 
@@ -109,6 +111,7 @@ func NewClient(options ...ConfigOption) (*Client, error) {
 		count:         make(chan metric),
 		sent:          make(chan MetricsData),
 		registered:    make(chan ClientData, 1),
+		impression:    make(chan ImpressionEvent, 10),
 		close:         make(chan struct{}),
 		closed:        make(chan struct{}),
 	}
@@ -135,6 +138,10 @@ func NewClient(options ...ConfigOption) (*Client, error) {
 	if mListener, ok := uc.options.listener.(MetricListener); ok {
 		uc.metricsListener = mListener
 	}
+	if iListener, ok := uc.options.listener.(ImpressionListener); ok {
+		uc.impressionListener = iListener
+	}
+
 	defer func() {
 		go uc.sync()
 	}()
@@ -251,6 +258,10 @@ func (uc *Client) sync() {
 			if uc.metricsListener != nil {
 				uc.metricsListener.OnRegistered(cd)
 			}
+		case ie := <-uc.impression:
+			if uc.impressionListener != nil {
+				uc.impressionListener.OnImpression(ie)
+			}
 		case <-uc.close:
 			close(uc.closed)
 			return
@@ -262,12 +273,32 @@ func (uc *Client) sync() {
 //
 // It is safe to call this method from multiple goroutines concurrently.
 func (uc *Client) IsEnabled(feature string, options ...FeatureOption) (enabled bool) {
+	result, f := uc.isEnabled(feature, options...)
+	enabled = result.Enabled
+
 	defer func() {
 		uc.metrics.count(feature, enabled)
+
+		if f != nil && f.ImpressionData && uc.impressionListener != nil {
+			var opts featureOption
+			for _, o := range options {
+				o(&opts)
+			}
+			ctx := uc.staticContext
+			if opts.ctx != nil {
+				ctx = ctx.Override(*opts.ctx)
+			}
+
+			uc.impression <- ImpressionEvent{
+				FeatureName: feature,
+				EventType:   ImpressionEventTypeIsEnabled,
+				Enabled:     enabled,
+				Context:     ctx,
+			}
+		}
 	}()
 
-	result, _ := uc.isEnabled(feature, options...)
-	return result.Enabled
+	return
 }
 
 // isEnabled abstracts away the details of checking if a toggle is turned on or off
@@ -400,12 +431,33 @@ func (uc *Client) isParentDependencySatisfied(feature *api.Feature, context cont
 // GetVariant queries a variant as the specified feature is enabled.
 //
 // It is safe to call this method from multiple goroutines concurrently.
-func (uc *Client) GetVariant(feature string, options ...VariantOption) *api.Variant {
-	variant := uc.getVariantWithoutMetrics(feature, options...)
+func (uc *Client) GetVariant(feature string, options ...VariantOption) (variant *api.Variant) {
+	variant = uc.getVariantWithoutMetrics(feature, options...)
+
 	defer func() {
 		uc.metrics.countVariants(feature, variant.FeatureEnabled, variant.Name)
+		
+		f := uc.repository.getToggle(feature)
+		if f != nil && f.ImpressionData && uc.impressionListener != nil {
+			var opts variantOption
+			for _, o := range options {
+				o(&opts)
+			}
+			ctx := uc.staticContext
+			if opts.ctx != nil {
+				ctx = ctx.Override(*opts.ctx)
+			}
+
+			uc.impression <- ImpressionEvent{
+				FeatureName: feature,
+				EventType:   ImpressionEventTypeGetVariant,
+				Enabled:     variant.FeatureEnabled,
+				Variant:     variant.Name,
+				Context:     ctx,
+			}
+		}
 	}()
-	return variant
+	return
 }
 
 // getVariantWithoutMetrics abstracts away the logic for resolving a variant without metrics
@@ -504,6 +556,11 @@ func (uc *Client) Registered() <-chan ClientData {
 	return uc.registered
 }
 
+// Impression returns the impression channel which gives an update when a feature with impression data has been evaluated.
+func (uc *Client) Impression() <-chan ImpressionEvent {
+	return uc.impression
+}
+
 // Sent returns the sent channel which receives data whenever the client has successfully sent metrics to
 // the metrics service.
 func (uc *Client) Sent() <-chan MetricsData {
diff --git a/debuglistener.go b/debuglistener.go
index 79526a9..f4a0f21 100644
--- a/debuglistener.go
+++ b/debuglistener.go
@@ -38,3 +38,8 @@ func (l DebugListener) OnSent(payload MetricsData) {
 func (l DebugListener) OnRegistered(payload ClientData) {
 	fmt.Printf("Registered: %+v\n", payload)
 }
+
+// OnImpression prints to the console for every user impression (IsEnabled / GetVariant)
+func (l DebugListener) OnImpression(event ImpressionEvent) {
+	fmt.Printf("Impression: %+v\n", event)
+}
diff --git a/impression.go b/impression.go
new file mode 100644
index 0000000..e69c262
--- /dev/null
+++ b/impression.go
@@ -0,0 +1,31 @@
+package unleash
+
+import (
+	"github.com/Unleash/unleash-client-go/v4/context"
+)
+
+type EventType string
+
+const (
+	ImpressionEventTypeIsEnabled EventType = "IsEnabled"
+	ImpressionEventTypeGetVariant EventType = "GetVariant"
+)
+
+// ImpressionEvent represents an event that occurs when a feature with impression data is evaluated.
+type ImpressionEvent struct {
+	// FeatureName is the name of the feature toggle that was evaluated.
+	FeatureName string
+
+	// EventType is the type of event that occurred (i.e., "IsEnabled" or "GetVariant").
+	EventType EventType
+
+	// Enabled indicates whether the feature was enabled or not.
+	Enabled bool
+
+	// Variant is the name of the variant that was resolved, if any.
+	Variant string
+
+	// Context is the evaluation context used.
+	Context *context.Context
+}
+
diff --git a/nooplistener.go b/nooplistener.go
index 64cf432..1c54a28 100644
--- a/nooplistener.go
+++ b/nooplistener.go
@@ -26,3 +26,7 @@ func (l NoopListener) OnSent(payload MetricsData) {
 // The client has registered.
 func (l NoopListener) OnRegistered(payload ClientData) {
 }
+
+// User impression (IsEnabled / GetVariant)
+func (l NoopListener) OnImpression(event ImpressionEvent) {
+}
diff --git a/unleash.go b/unleash.go
index c4c36eb..d0635c2 100644
--- a/unleash.go
+++ b/unleash.go
@@ -35,6 +35,13 @@ type RepositoryListener interface {
 	OnReady()
 }
 
+// ImpressionListener defines an interface that can be implemented in order to receive
+// impression events when a feature with impression data is evaluated.
+type ImpressionListener interface {
+	// OnImpression is called whenever a feature with impression data is evaluated.
+	OnImpression(ImpressionEvent)
+}
+
 // IsEnabled queries the default client whether or not the specified feature is enabled or not.
 func IsEnabled(feature string, options ...FeatureOption) bool {
 	if defaultClient == nil {
diff --git a/unleash_mock.go b/unleash_mock.go
index 76be4de..2d440e3 100644
--- a/unleash_mock.go
+++ b/unleash_mock.go
@@ -41,6 +41,10 @@ func (l *MockedListener) OnRegistered(payload ClientData) {
 	l.Called(payload)
 }
 
+func (l *MockedListener) OnImpression(event ImpressionEvent) {
+	l.Called(event)
+}
+
 func writeJSON(rw http.ResponseWriter, x interface{}) {
 	enc := json.NewEncoder(rw)
 	if err := enc.Encode(x); err != nil {


Test patch:
diff --git a/api/variant_test.go b/api/variant_test.go
index e834f70..ce669d1 100644
--- a/api/variant_test.go
+++ b/api/variant_test.go
@@ -331,3 +331,13 @@ func (suite *VariantTestSuite) TestGetVariant_OverrideOnEnvironment() {
 	suite.Equal(true, variantSetup.Enabled, "Should be equal")
 	suite.Equal(expectedPayload, variantSetup.Payload, "Should be equal")
 }
+
+func TestGetDefaultVariant(t *testing.T) {
+	defaultVariant := GetDefaultVariant()
+	if defaultVariant.Name != "disabled" {
+		t.Errorf("Expected default variant name to be 'disabled', got '%s'", defaultVariant.Name)
+	}
+	if defaultVariant.Enabled {
+		t.Error("Expected default variant to be disabled")
+	}
+}
diff --git a/benchmark_test.go b/benchmark_test.go
index 672b04d..da8f425 100644
--- a/benchmark_test.go
+++ b/benchmark_test.go
@@ -9,18 +9,9 @@ import (
 	"github.com/Unleash/unleash-client-go/v4"
 )
 
-type NoOpListener struct{}
-
-func (l *NoOpListener) OnReady()                                {}
-func (l *NoOpListener) OnError(err error)                       {}
-func (l *NoOpListener) OnWarning(warning error)                 {}
-func (l *NoOpListener) OnCount(name string, enabled bool)       {}
-func (l *NoOpListener) OnSent(payload unleash.MetricsData)      {}
-func (l *NoOpListener) OnRegistered(payload unleash.ClientData) {}
-
 func BenchmarkFeatureToggleEvaluation(b *testing.B) {
 	unleash.Initialize(
-		unleash.WithListener(&NoOpListener{}),
+		unleash.WithListener(&unleash.NoopListener{}),
 		unleash.WithAppName("go-benchmark"),
 		unleash.WithUrl("https://app.unleash-hosted.com/demo/api/"),
 		unleash.WithCustomHeaders(http.Header{"Authorization": {"Go-Benchmark:development.be6b5d318c8e77469efb58590022bb6416100261accf95a15046c04d"}}),
diff --git a/example_with_instance_test.go b/example_with_instance_test.go
index b75be77..1b114dc 100644
--- a/example_with_instance_test.go
+++ b/example_with_instance_test.go
@@ -24,6 +24,8 @@ func Sync(client *unleash.Client) {
 			fmt.Printf("SENT: %+v\n", md)
 		case cd := <-client.Registered():
 			fmt.Printf("REGISTERED: %+v\n", cd)
+		case ie := <-client.Impression():
+			fmt.Printf("IMPRESSION: %+v\n", ie)
 		case <-timer.C:
 			fmt.Printf("ISENABLED: %v\n", client.IsEnabled("eid.enabled"))
 			timer.Reset(1 * time.Second)
diff --git a/impression_test.go b/impression_test.go
new file mode 100644
index 0000000..addde6f
--- /dev/null
+++ b/impression_test.go
@@ -0,0 +1,442 @@
+package unleash
+
+import (
+	"sync"
+	"testing"
+
+	"github.com/Unleash/unleash-client-go/v4/api"
+	"github.com/Unleash/unleash-client-go/v4/context"
+	"github.com/h2non/gock"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/mock"
+)
+
+func setupClient(t *testing.T, listener *MockedListener) *Client {
+	client, err := NewClient(
+		WithUrl(mockerServer),
+		WithAppName(mockAppName),
+		WithInstanceId(mockInstanceId),
+		WithListener(listener),
+	)
+	assert.NoError(t, err)
+	client.WaitForReady()
+	return client
+}
+
+func TestImpression_Off(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "impression-data-off"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:    feature,
+					Enabled: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Return()
+	mockListener.On("OnImpression", mock.Anything).Maybe()
+
+	client := setupClient(t, mockListener)
+	client.IsEnabled(feature)
+
+	assert.NoError(client.Close())
+	mockListener.AssertNotCalled(t, "OnImpression", mock.Anything)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}
+
+func TestImpression_IsEnabled(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "impression-data-on"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:           feature,
+					Enabled:        true,
+					ImpressionData: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Return()
+
+	mockListener.On("OnImpression", mock.MatchedBy(func(e ImpressionEvent) bool {
+		return e.FeatureName == feature &&
+			e.EventType == ImpressionEventTypeIsEnabled &&
+			e.Enabled == true
+	})).Run(func(args mock.Arguments) {
+		wg.Done()
+	}).Once()
+
+	client := setupClient(t, mockListener)
+	client.IsEnabled(feature)
+
+	wg.Wait()
+	assert.NoError(client.Close())
+	mockListener.AssertExpectations(t)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}
+
+func TestImpression_GetVariant(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "impression-data-on-variant"
+	variant := "variant-for-impression-data"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:           feature,
+					Enabled:        true,
+					ImpressionData: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+								"groupId":    variant,
+							},
+							Variants: []api.VariantInternal{
+								{
+									Variant: api.Variant{
+										Name: variant,
+										Payload: api.Payload{
+											Type:  "string",
+											Value: "strategyVariantValue",
+										},
+									},
+									Weight: 1000,
+								},
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Return()
+
+	mockListener.On("OnImpression", mock.MatchedBy(func(e ImpressionEvent) bool {
+		return e.FeatureName == feature &&
+			e.EventType == ImpressionEventTypeGetVariant &&
+			e.Enabled == true &&
+			e.Variant == variant
+	})).Run(func(args mock.Arguments) {
+		wg.Done()
+	}).Once()
+
+	client := setupClient(t, mockListener)
+	client.GetVariant(feature)
+
+	wg.Wait()
+	assert.NoError(client.Close())
+	mockListener.AssertExpectations(t)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}
+
+func TestImpression_WithContext(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "with-context"
+	ctxUserId := "123"
+	ctxSessionId := "abc"
+	ctxPropertyId := "impression-data-ctx-id"
+	ctxPropertyValue := "impression-data-ctx-value"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:           feature,
+					Enabled:        true,
+					ImpressionData: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+							},
+							Constraints: []api.Constraint{
+								{
+									ContextName: ctxPropertyId,
+									Operator:    api.OperatorIn,
+									Values:      []string{ctxPropertyValue},
+								},
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Return()
+
+	userCtx := context.Context{
+		UserId:    ctxUserId,
+		SessionId: ctxSessionId,
+		Properties: map[string]string{
+			ctxPropertyId: ctxPropertyValue,
+		},
+	}
+
+	mockListener.On("OnImpression", mock.MatchedBy(func(e ImpressionEvent) bool {
+		return e.FeatureName == feature &&
+			e.EventType == ImpressionEventTypeIsEnabled &&
+			e.Enabled == true &&
+			e.Context != nil &&
+			e.Context.UserId == ctxUserId &&
+			e.Context.SessionId == ctxSessionId &&
+			e.Context.Properties[ctxPropertyId] == ctxPropertyValue
+	})).Run(func(args mock.Arguments) {
+		wg.Done()
+	}).Once()
+
+	client := setupClient(t, mockListener)
+	client.IsEnabled(feature, WithContext(userCtx))
+
+	wg.Wait()
+	assert.NoError(client.Close())
+	mockListener.AssertExpectations(t)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}
+
+func TestImpression_WithContextAndMultipleEvents(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+	var wg sync.WaitGroup
+	wg.Add(2)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "context-multiple-evals"
+	ctxUserId := "123"
+	ctxSessionId := "abc"
+	ctxPropertyId := "ctx-key"
+	ctxPropertyValue := "ctx-val"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:           feature,
+					Enabled:        true,
+					ImpressionData: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+							},
+							Constraints: []api.Constraint{
+								{
+									ContextName: ctxPropertyId,
+									Operator:    api.OperatorIn,
+									Values:      []string{ctxPropertyValue},
+								},
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Maybe()
+	mockListener.On("OnCount", feature, false).Maybe()
+
+	userCtx := context.Context{
+		UserId:    ctxUserId,
+		SessionId: ctxSessionId,
+		Properties: map[string]string{
+			ctxPropertyId: ctxPropertyValue,
+		},
+	}
+
+	mockListener.On("OnImpression", mock.MatchedBy(func(e ImpressionEvent) bool {
+		return e.FeatureName == feature &&
+			e.EventType == ImpressionEventTypeIsEnabled &&
+			e.Enabled == true &&
+			e.Context != nil &&
+			e.Context.UserId == ctxUserId &&
+			e.Context.SessionId == ctxSessionId &&
+			e.Context.Properties[ctxPropertyId] == ctxPropertyValue
+	})).Run(func(args mock.Arguments) {
+		wg.Done()
+	}).Once()
+
+	mockListener.On("OnImpression", mock.MatchedBy(func(e ImpressionEvent) bool {
+		return e.FeatureName == feature &&
+			e.EventType == ImpressionEventTypeIsEnabled &&
+			e.Enabled == false &&
+			len(e.Context.Properties) == 0
+	})).Run(func(args mock.Arguments) {
+		wg.Done()
+	}).Once()
+
+	client := setupClient(t, mockListener)
+
+	resultWithCtx := client.IsEnabled(feature, WithContext(userCtx))
+	assert.True(resultWithCtx)
+
+	resultWithoutCtx := client.IsEnabled(feature)
+	assert.False(resultWithoutCtx)
+
+	wg.Wait()
+	assert.NoError(client.Close())
+	mockListener.AssertExpectations(t)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}
+
+func TestImpression_GetChannelMethod(t *testing.T) {
+	defer gock.OffAll()
+	assert := assert.New(t)
+	var wg sync.WaitGroup
+	wg.Add(1)
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	feature := "impression-get-channel-method-test"
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: []api.Feature{
+				{
+					Name:           feature,
+					Enabled:        true,
+					ImpressionData: true,
+					Strategies: []api.Strategy{
+						{
+							Id:   1,
+							Name: "flexibleRollout",
+							Parameters: map[string]interface{}{
+								"rollout":    100,
+								"stickiness": "default",
+							},
+						},
+					},
+				},
+			},
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+	mockListener.On("OnCount", feature, true).Return()
+	mockListener.On("OnImpression", mock.AnythingOfType("ImpressionEvent")).Maybe()
+	mockListener.On("OnError", mock.Anything).Maybe()
+
+	client := setupClient(t, mockListener)
+	impressionChannel := client.Impression()
+
+	ready := make(chan struct{})
+
+	go func() {
+		close(ready)
+		for impression := range impressionChannel {
+			if impression.FeatureName == feature &&
+				impression.EventType == ImpressionEventTypeIsEnabled &&
+				impression.Enabled {
+				wg.Done()
+				return
+			}
+		}
+	}()
+
+	<-ready
+	client.IsEnabled(feature)
+
+	wg.Wait()
+	assert.NoError(client.Close())
+	mockListener.AssertExpectations(t)
+	assert.True(gock.IsDone(), "there should be no more mocks")
+}