From 13d17b3d3c3da6e3b80315c3d22f2eeaf8c821cd Mon Sep 17 00:00:00 2001 From: Mykola Rybak Date: Fri, 29 Aug 2025 15:02:40 +0300 Subject: [PATCH 1/2] Support keys and configs in SplitClientForTest SplitClientForTest can be useful for unit-testing code that depends on SplitClient, especially in dynamic scenarios where a static config per test class won't work. As of now, SplitClientForTest doesn't support customization of split keys or configs - it always returns the same treatment for all keys of a feature flag and always returns null as a split config. This change extends SplitClientForTest, so it's possible to set treatments for each feature flag and key, and also possible to set split config in addition to the treatment name. --- .../client/testing/SplitClientForTest.java | 120 ++++++++---------- 1 file changed, 54 insertions(+), 66 deletions(-) diff --git a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java index 9ee4b8c3e..124adba0d 100644 --- a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java +++ b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java @@ -1,5 +1,6 @@ package io.split.client.testing; +import io.split.client.SplitAndKey; import io.split.client.SplitClient; import io.split.client.api.Key; import io.split.client.api.SplitResult; @@ -10,130 +11,117 @@ import java.util.concurrent.TimeoutException; public class SplitClientForTest implements SplitClient { - private Map _tests; + private static final SplitResult CONTROL_RESULT = new SplitResult(Treatments.CONTROL, null); + + private Map _tests; public SplitClientForTest() { _tests = new HashMap<>(); } - public Map tests() { + public Map tests() { return _tests; } public void clearTreatments() { - _tests = new HashMap<>(); + _tests.clear(); } public void registerTreatments(Map treatments) { - _tests.putAll(treatments); + for (Map.Entry entry : treatments.entrySet()) { + registerTreatment(entry.getKey(), entry.getValue()); + } } public void registerTreatment(String feature, String treatment) { - _tests.put(feature, treatment); + registerTreatment(feature, null, treatment); + } + + public void registerTreatment(String feature, String key, String treatment) { + registerTreatment(feature, key, treatment, null); + } + + public void registerTreatment(String feature, String key, String treatment, String config) { + registerTreatment(SplitAndKey.of(feature, key), new SplitResult(treatment, config)); + } + + public void registerTreatment(SplitAndKey splitAndKey, SplitResult splitResult) { + _tests.put(splitAndKey, splitResult); } public String getTreatment(String key, String featureFlagName) { - return _tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL; + return getTreatment(key, featureFlagName, Collections.emptyMap()); } public String getTreatment(String key, String featureFlagName, Map attributes) { - return _tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL; + return getTreatmentWithConfig(key, featureFlagName, attributes).treatment(); } public String getTreatment(Key key, String featureFlagName, Map attributes) { - return _tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL; + return getTreatment(key.matchingKey(), featureFlagName, attributes); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName) { - return new SplitResult(_tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL, null); + return getTreatmentWithConfig(key, featureFlagName, Collections.emptyMap()); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes) { - return new SplitResult(_tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL, null); + if (_tests.containsKey(SplitAndKey.of(featureFlagName, key))) { + return _tests.get(SplitAndKey.of(featureFlagName, key)); + } + else { + return _tests.getOrDefault(SplitAndKey.of(featureFlagName), CONTROL_RESULT); + } } @Override public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes) { - return new SplitResult(_tests.containsKey(featureFlagName) - ? _tests.get(featureFlagName) - : Treatments.CONTROL, null); + return getTreatmentWithConfig(key.matchingKey(), featureFlagName, attributes); } @Override public Map getTreatments(String key, List featureFlagNames) { - Map treatments = new HashMap<>(); - for (String split : featureFlagNames) { - treatments.put(split, _tests.containsKey(split) ? _tests.get(split) : Treatments.CONTROL); - } - return treatments; + return getTreatments(key, featureFlagNames, Collections.emptyMap()); } @Override public Map getTreatments(String key, List featureFlagNames, Map attributes){ Map treatments = new HashMap<>(); for (String split : featureFlagNames) { - treatments.put(split, _tests.containsKey(split) ? _tests.get(split) : Treatments.CONTROL); + treatments.put(split, getTreatment(key, split, attributes)); } return treatments; } @Override public Map getTreatments(Key key, List featureFlagNames, Map attributes) { - Map treatments = new HashMap<>(); - for (String split : featureFlagNames) { - treatments.put(split, _tests.containsKey(split) ? _tests.get(split) : Treatments.CONTROL); - } - return treatments; + return getTreatments(key.matchingKey(), featureFlagNames, attributes); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames) { - Map treatments = new HashMap<>(); - for (String split : featureFlagNames) { - treatments.put(split, new SplitResult(_tests.containsKey(split) - ? _tests.get(split) - : Treatments.CONTROL, null)); - } - return treatments; + return getTreatmentsWithConfig(key, featureFlagNames, Collections.emptyMap()); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes) { Map treatments = new HashMap<>(); for (String split : featureFlagNames) { - treatments.put(split, new SplitResult(_tests.containsKey(split) - ? _tests.get(split) - : Treatments.CONTROL, null)); + treatments.put(split, getTreatmentWithConfig(key, split, attributes)); } return treatments; } @Override public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes) { - Map treatments = new HashMap<>(); - for (String split : featureFlagNames) { - treatments.put(split, new SplitResult(_tests.containsKey(split) - ? _tests.get(split) - : Treatments.CONTROL, null)); - } - return treatments; + return getTreatmentsWithConfig(key.matchingKey(), featureFlagNames, attributes); } @Override public Map getTreatmentsByFlagSet(String key, String flagSet) { - return null; + return new HashMap<>(); } @Override @@ -148,7 +136,7 @@ public Map getTreatmentsByFlagSet(Key key, String flagSet, Map getTreatmentsByFlagSets(String key, List flagSets) { - return null; + return new HashMap<>(); } @Override @@ -163,7 +151,7 @@ public Map getTreatmentsByFlagSets(Key key, List flagSet @Override public Map getTreatmentsWithConfigByFlagSet(String key, String flagSet) { - return null; + return new HashMap<>(); } @Override @@ -178,7 +166,7 @@ public Map getTreatmentsWithConfigByFlagSet(Key key, String @Override public Map getTreatmentsWithConfigByFlagSets(String key, List flagSets) { - return null; + return new HashMap<>(); } @Override @@ -193,62 +181,62 @@ public Map getTreatmentsWithConfigByFlagSets(Key key, List< @Override public String getTreatment(String key, String featureFlagName, EvaluationOptions evaluationOptions) { - return null; + return getTreatment(key, featureFlagName); } @Override public String getTreatment(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { - return null; + return getTreatment(key, featureFlagName, attributes); } @Override public String getTreatment(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { - return null; + return getTreatment(key, featureFlagName, attributes); } @Override public Map getTreatments(String key, List featureFlagNames, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatments(key, featureFlagNames); } @Override public Map getTreatments(String key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatments(key, featureFlagNames, attributes); } @Override public Map getTreatments(Key key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatments(key, featureFlagNames, attributes); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName, EvaluationOptions evaluationOptions) { - return null; + return getTreatmentWithConfig(key, featureFlagName); } @Override public SplitResult getTreatmentWithConfig(Key key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { - return null; + return getTreatmentWithConfig(key, featureFlagName, attributes); } @Override public SplitResult getTreatmentWithConfig(String key, String featureFlagName, Map attributes, EvaluationOptions evaluationOptions) { - return null; + return getTreatmentWithConfig(key, featureFlagName, attributes); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatmentsWithConfig(key, featureFlagNames, attributes); } @Override public Map getTreatmentsWithConfig(String key, List featureFlagNames, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatmentsWithConfig(key, featureFlagNames); } @Override From 2d7c67dd767a9f3211a9c568878040a4622f1d31 Mon Sep 17 00:00:00 2001 From: Mykola Rybak Date: Sat, 4 Apr 2026 01:17:40 +0300 Subject: [PATCH 2/2] Fix SplitClientForTest compatibility Fix compatibility for the SplitClientForTest.tests() method. Keep it with the old signature, expose new complete mappings via testMappings() method. --- .../client/testing/SplitClientForTest.java | 21 +++++++-- .../client/testing/runner/RunWithSplits.java | 10 +++-- .../testing/SplitScenarioAnnotationTest.java | 45 ++++++++++--------- 3 files changed, 47 insertions(+), 29 deletions(-) diff --git a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java index 124adba0d..763d4df5b 100644 --- a/testing/src/main/java/io/split/client/testing/SplitClientForTest.java +++ b/testing/src/main/java/io/split/client/testing/SplitClientForTest.java @@ -10,17 +10,30 @@ import java.util.*; import java.util.concurrent.TimeoutException; +import static java.util.stream.Collectors.toMap; + public class SplitClientForTest implements SplitClient { private static final SplitResult CONTROL_RESULT = new SplitResult(Treatments.CONTROL, null); - private Map _tests; + private final Map _tests; public SplitClientForTest() { _tests = new HashMap<>(); } - public Map tests() { - return _tests; + public Map tests() { + return _tests + .entrySet() + .stream() + .collect(toMap( + entry -> entry.getKey().split(), + entry -> entry.getValue().treatment(), + (existing, replacement) -> existing + )); + } + + public Map testMappings() { + return Collections.unmodifiableMap(_tests); } public void clearTreatments() { @@ -286,7 +299,7 @@ public Map getTreatmentsByFlagSet(String key, String flagSet, Ev @Override public Map getTreatmentsWithConfig(Key key, List featureFlagNames, Map attributes, EvaluationOptions evaluationOptions) { - return new HashMap<>(); + return getTreatmentsWithConfig(key.matchingKey(), featureFlagNames); } @Override diff --git a/testing/src/main/java/io/split/client/testing/runner/RunWithSplits.java b/testing/src/main/java/io/split/client/testing/runner/RunWithSplits.java index d6b864ad4..01c6d3ee3 100644 --- a/testing/src/main/java/io/split/client/testing/runner/RunWithSplits.java +++ b/testing/src/main/java/io/split/client/testing/runner/RunWithSplits.java @@ -1,5 +1,7 @@ package io.split.client.testing.runner; +import io.split.client.SplitAndKey; +import io.split.client.api.SplitResult; import io.split.client.testing.SplitClientForTest; import io.split.client.testing.annotations.SplitTestClient; import org.junit.runners.model.Statement; @@ -24,7 +26,7 @@ public void evaluate() throws Throwable { SplitClientForTest splitClient = findFirstSplitClient(target, target.getClass()); // Preserve the Split state between Test runs - Map priorTests = new HashMap<>(splitClient.tests()); + Map priorTests = new HashMap<>(splitClient.testMappings()); // Apply the Active Scenario for this if (scenario != null) { @@ -36,7 +38,9 @@ public void evaluate() throws Throwable { } finally { // Clear any Scenario specific changes and re-apply existing splits splitClient.clearTreatments(); - splitClient.registerTreatments(priorTests); + for (Map.Entry entry : priorTests.entrySet()) { + splitClient.registerTreatment(entry.getKey(), entry.getValue()); + } } } @@ -57,4 +61,4 @@ private static SplitClientForTest findFirstSplitClient(Object target, Class t throw new IllegalArgumentException("No SplitTestClient found in hierarchy"); } } -} \ No newline at end of file +} diff --git a/testing/src/test/java/io/split/client/testing/SplitScenarioAnnotationTest.java b/testing/src/test/java/io/split/client/testing/SplitScenarioAnnotationTest.java index 84a772ceb..f590d0c5d 100644 --- a/testing/src/test/java/io/split/client/testing/SplitScenarioAnnotationTest.java +++ b/testing/src/test/java/io/split/client/testing/SplitScenarioAnnotationTest.java @@ -1,6 +1,8 @@ package io.split.client.testing; +import com.google.common.collect.ImmutableMap; import io.split.client.api.Key; +import io.split.client.api.SplitResult; import io.split.client.dtos.EvaluationOptions; import io.split.client.testing.annotations.SplitScenario; import io.split.client.testing.annotations.SplitSuite; @@ -12,7 +14,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Objects; @@ -78,29 +79,29 @@ public void testDefaultScenario() { Assert.assertEquals(CONTROL_TREATMENT, splitClient.getTreatment(ARBITRARY_KEY, CONTROL_FEATURE)); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset", new HashMap<>())); - Assert.assertEquals(null, splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset")); - Assert.assertEquals(null, splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset")); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset")); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset")); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"), new HashMap<>())); - Assert.assertEquals(null, splitClient.getTreatmentsByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); - Assert.assertEquals(null, splitClient.getTreatmentsByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSet(ARBITRARY_KEY, "flagset", new HashMap<>())); - Assert.assertEquals(null, splitClient.getTreatmentsWithConfigByFlagSet(ARBITRARY_KEY, "flagset")); - Assert.assertEquals(null, splitClient.getTreatmentsWithConfigByFlagSet(ARBITRARY_KEY, "flagset")); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSet(ARBITRARY_KEY, "flagset")); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSet(ARBITRARY_KEY, "flagset")); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"), new HashMap<>())); - Assert.assertEquals(null, splitClient.getTreatmentsWithConfigByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); - Assert.assertEquals(null, splitClient.getTreatmentsWithConfigByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); - - Assert.assertEquals(null, splitClient.getTreatment(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatment(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatment(new Key(ARBITRARY_KEY, ARBITRARY_KEY), DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatmentWithConfig(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatmentWithConfig(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatmentWithConfig(new Key(ARBITRARY_KEY, ARBITRARY_KEY), DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(new HashMap<>(), splitClient.getTreatments(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(new HashMap<>(), splitClient.getTreatments(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfig(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new EvaluationOptions(new HashMap<>()))); - Assert.assertEquals(null, splitClient.getTreatmentsWithConfig(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>())).get(DEFAULT_CLIENT_FEATURE)); - Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfig(new Key(ARBITRARY_KEY, ARBITRARY_KEY), Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); + Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsWithConfigByFlagSets(ARBITRARY_KEY, Arrays.asList("flagset"))); + + Assert.assertEquals(ON_TREATMENT, splitClient.getTreatment(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(ON_TREATMENT, splitClient.getTreatment(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(ON_TREATMENT, splitClient.getTreatment(new Key(ARBITRARY_KEY, ARBITRARY_KEY), DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(new SplitResult(ON_TREATMENT, null), splitClient.getTreatmentWithConfig(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(new SplitResult(ON_TREATMENT, null), splitClient.getTreatmentWithConfig(ARBITRARY_KEY, DEFAULT_CLIENT_FEATURE, new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(new SplitResult(ON_TREATMENT, null), splitClient.getTreatmentWithConfig(new Key(ARBITRARY_KEY, ARBITRARY_KEY), DEFAULT_CLIENT_FEATURE, new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(ImmutableMap.of(DEFAULT_CLIENT_FEATURE, ON_TREATMENT), splitClient.getTreatments(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(ImmutableMap.of(DEFAULT_CLIENT_FEATURE, ON_TREATMENT), splitClient.getTreatments(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(ImmutableMap.of(DEFAULT_CLIENT_FEATURE, new SplitResult(ON_TREATMENT, null)), splitClient.getTreatmentsWithConfig(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new EvaluationOptions(new HashMap<>()))); + Assert.assertEquals(new SplitResult(ON_TREATMENT, null), splitClient.getTreatmentsWithConfig(ARBITRARY_KEY, Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>())).get(DEFAULT_CLIENT_FEATURE)); + Assert.assertEquals(ImmutableMap.of(DEFAULT_CLIENT_FEATURE, new SplitResult(ON_TREATMENT, null)), splitClient.getTreatmentsWithConfig(new Key(ARBITRARY_KEY, ARBITRARY_KEY), Arrays.asList(DEFAULT_CLIENT_FEATURE), new HashMap<>(), new EvaluationOptions(new HashMap<>()))); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset", new HashMap<>(), new EvaluationOptions(new HashMap<>()))); Assert.assertEquals(new HashMap<>(), splitClient.getTreatmentsByFlagSet(ARBITRARY_KEY, "flagset", new EvaluationOptions(new HashMap<>()))); @@ -220,4 +221,4 @@ public void testScenariosAnnotation() { Assert.assertEquals(ON_TREATMENT, splitClient.getTreatment(ARBITRARY_KEY, "SCENARIO_FEATURE_3")); } } -} \ No newline at end of file +}