Repository: Unleash/unleash-client-java
Pull request #125 (2021-02-08 17:19:39)

feat: Add ability to disable metrics via UnleashContext
Addresses #124 

Refined approach with `more` concept for evaluating all toggles as well as counting metrics.

Example usage:
```
     @PostMapping("/features")
    @ResponseBody
    public List<EvaluatedToggle> featureMetrics(@RequestBody List<FeatureUsage> usage){
        usage.forEach(toggle -> {
            for (int i = 0; i < toggle.count; i++) {
                unleash.more().count(toggle.getName(), toggle.isEnabled());
            }
        });

        UnleashContext cont = createContext();
        return unleash.more().evaluateAllToggles(cont);
    }
```

This returns a json of features, I might refine this response in my service to strip out nulls/empty variants:
```
[
  {
    "enabled": true,
    "name": "feature",
    "variant": {
      "name": "disabled",
      "payload": {
        "type": "string",
        "value": null
      },
      "enabled": false
    }
  },
  {
    "enabled": false,
    "name": "other-feature",
    "variant": {
      "name": "disabled",
      "payload": {
        "type": "string",
        "value": null
      },
      "enabled": false
    }
  },
  {
    "enabled": false,
    "name": "user-feature",
    "variant": {
      "name": "disabled",
      "payload": {
        "type": "string",
        "value": null
      },
      "enabled": false
    }
  }
]
```

And receives a list of feature checks so we can poke the metrics:
```
[{
"name": "myToggle",
"enabled": true,
"count": 15
}]
```


(I apologize for the code formatting commit, but maven:spotless insisted they were needed, so I included them. I can separate them out if you want) 

checketts: The spotless updates were fallout from my Nullability PR earlier. So they were legitimate changes.

I had forgotten to add a changelog entry. That is addressed now. However once #123 is merged it will probably create a merge-conflict, so I'll be sure to rebase to resolve that when it happens.
ivarconr: Hi again @checketts,

Really happy you take imitative and sending this PR! 

You have found a very pragmatic solution which require a minimal set of changes to satisfy the need of disabling metrics. I have been thinking about this change a few times today and I have concluded that I do not think this PR represent the correct approach to this problem. I am afraid that the `UnleashContext` object ends up being overloaded with overrides for how the SDK should work. The intended purpose of the `UnleashContext` object is to carry data from the application to the SDK, in order for the SDK to perform the desired action and return an result. 

I would rather see a specific function for this use-case where you send in the UnleashContext as is, and get back a list of evaluated feature toggles for that specific context. I think we can take some inspiration on how the [Unleash-Proxy actually work](https://www.unleash-hosted.com/docs/working-with-the-unleash-proxy/):

How it could look like from code using the SDK:
(I have yet not figured out what to call the `SomeResultObject`)

```java
List<SomeResultObject> result = unleash.evaluateAllToggles(unleashContext);
```

The shape of the List of objects should probably be somehting in the line of:
```

[{
		"enabled": true,
		"name": "SimpleToggle",
		"variant": {
			"enabled": false,
			"name": "disabled"
		}
	},
	{
		"enabled": true,
		"name": "ToggleWithVariant",
		"variant": {
			"enabled": true,
			"name": "brown"
		}
	},
	{
		"enabled": true,
		"name": "ToggleWithVariantAndPayload",
		"variant": {
			"enabled": true,
			"name": "default",
			"payload": {
				"type": "string",
				"value": "Some custom value here!"
			}
		}
	}
]
checketts: @ivarconr Thanks for the feedback. I'm happy to change approaches and improve the implementation.

To summarize adding directly to the `Unleash` interface is the preferred approach in this case? Or would you prefer to keep that interface as simple as possible and instead add it behind a `batch` operations api?

For example: 
`unleash.evaluateAllToggles()` versus `unleash.batch().evaluateAllToggles()`

The interface returned by the `batch` call would keep the unusual calls from cluttering the common API usecase. (In another project they used `more()` as the name to the advanced sub-api).
ivarconr: > Or would you prefer to keep that interface as simple as possible and instead add it behind a batch operations api?

This sound very appealing. I have not seen this before, but it feels like the right approach, and possible the `getFeatureToggleNames` fits better behind the batch API as well. 

Would you have any link/examples on the `more()` approach?
checketts: Micrometer is a metrics facade (similar to slf4j, but for metrics). It has a `more()` concept.

Essentially there are many common ways to use metrics and the advanced or unusual ones that might confuse folks exist in the `more` API.

The method exposing it:
https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java#L454

Implementation:
https://github.com/micrometer-metrics/micrometer/blob/master/micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java#L846

I'll take a stab at that approach later today and update the PR along with examples.
checketts: I updated the original description with usage examples.
ivarconr: I must say the updated changes looks really good, and it makes perfect sense to start introducing these more advanced use cases as part of a `MoreOptions` interface. Thanks for taking the time to improve the initial implementation. 

I feel comfortable merging and releasing this. 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e073a65..8835b319 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@
 - fix: BREAKING CHANGE UnleashConfig constructor is now private.
   Use UnleashConfig.Builder to get instance of UnleashConfig
 - feat: Add annotations to indicate null and nonnull method signatures, also supports Kotlin
+- feat: Added 'more' method to Unleash API to place advanced usecases like evaluating all toggles at once and manually incrementing usage counts
 - feat: Add warning when a matching strategy for a toggle can't be found
 
 ## 3.3.4
diff --git a/src/main/java/no/finn/unleash/DefaultUnleash.java b/src/main/java/no/finn/unleash/DefaultUnleash.java
index 67d3abf5..ce47d242 100644
--- a/src/main/java/no/finn/unleash/DefaultUnleash.java
+++ b/src/main/java/no/finn/unleash/DefaultUnleash.java
@@ -10,6 +10,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+
 import no.finn.unleash.event.EventDispatcher;
 import no.finn.unleash.event.ToggleEvaluated;
 import no.finn.unleash.lang.Nullable;
@@ -61,13 +63,29 @@ public final class DefaultUnleash implements Unleash {
             UnleashConfig unleashConfig,
             ToggleRepository toggleRepository,
             Strategy... strategies) {
+        this(unleashConfig,
+            toggleRepository,
+            buildStrategyMap(strategies),
+            unleashConfig.getContextProvider(),
+            new EventDispatcher(unleashConfig),
+            new UnleashMetricServiceImpl(unleashConfig, unleashConfig.getScheduledExecutor())
+            );
+    }
+
+    // Visible for testing
+    public DefaultUnleash(
+        UnleashConfig unleashConfig,
+        ToggleRepository toggleRepository,
+        Map<String, Strategy> strategyMap,
+        UnleashContextProvider contextProvider,
+        EventDispatcher eventDispatcher,
+        UnleashMetricService metricService) {
         this.config = unleashConfig;
         this.toggleRepository = toggleRepository;
-        this.strategyMap = buildStrategyMap(strategies);
-        this.contextProvider = unleashConfig.getContextProvider();
-        this.eventDispatcher = new EventDispatcher(unleashConfig);
-        this.metricService =
-                new UnleashMetricServiceImpl(unleashConfig, unleashConfig.getScheduledExecutor());
+        this.strategyMap = strategyMap;
+        this.contextProvider = contextProvider;
+        this.eventDispatcher = eventDispatcher;
+        this.metricService = metricService;
         metricService.register(strategyMap.keySet());
     }
 
@@ -170,15 +188,24 @@ public final class DefaultUnleash implements Unleash {
         return ofNullable(toggleRepository.getToggle(toggleName));
     }
 
+    /**
+     * Use more().getFeatureToggleNames() instead
+     * @return a list of known toggle names
+     */
+    @Deprecated()
     public List<String> getFeatureToggleNames() {
         return toggleRepository.getFeatureNames();
     }
 
+    /**
+     * Use more().count() instead
+     */
+    @Deprecated
     public void count(final String toggleName, boolean enabled) {
         metricService.count(toggleName, enabled);
     }
 
-    private Map<String, Strategy> buildStrategyMap(@Nullable Strategy[] strategies) {
+    private static Map<String, Strategy> buildStrategyMap(@Nullable Strategy[] strategies) {
         Map<String, Strategy> map = new HashMap<>();
 
         BUILTIN_STRATEGIES.forEach(strategy -> map.put(strategy.getName(), strategy));
@@ -200,4 +227,43 @@ public final class DefaultUnleash implements Unleash {
     public void shutdown() {
         config.getScheduledExecutor().shutdown();
     }
+
+    @Override
+    public MoreOperations more() { return new DefaultMore(); }
+
+    public class DefaultMore implements MoreOperations {
+
+        @Override
+        public List<String> getFeatureToggleNames() {
+            return toggleRepository.getFeatureNames();
+        }
+
+        @Override
+        public List<EvaluatedToggle> evaluateAllToggles() {
+            return evaluateAllToggles(contextProvider.getContext());
+        }
+
+        @Override
+        public List<EvaluatedToggle> evaluateAllToggles(UnleashContext context) {
+            return getFeatureToggleNames().stream().map(toggleName -> {
+                boolean enabled = checkEnabled(toggleName, context, (n,c) -> false);
+                FeatureToggle featureToggle = toggleRepository.getToggle(toggleName);
+                Variant variant = enabled ? selectVariant(featureToggle, context, DISABLED_VARIANT) : DISABLED_VARIANT;
+
+                return new EvaluatedToggle(toggleName, enabled, variant);
+            }).collect(Collectors.toList());
+        }
+
+        @Override
+        public void count(final String toggleName, boolean enabled) {
+            metricService.count(toggleName, enabled);
+        }
+
+        @Override
+        public void countVariant(final String toggleName, String variantName) {
+            metricService.countVariant(toggleName, variantName);
+        }
+
+
+    }
 }
diff --git a/src/main/java/no/finn/unleash/EvaluatedToggle.java b/src/main/java/no/finn/unleash/EvaluatedToggle.java
new file mode 100644
index 00000000..b637e30c
--- /dev/null
+++ b/src/main/java/no/finn/unleash/EvaluatedToggle.java
@@ -0,0 +1,28 @@
+package no.finn.unleash;
+
+import no.finn.unleash.lang.Nullable;
+
+public class EvaluatedToggle {
+    private final boolean enabled;
+    private final String name;
+    @Nullable private final Variant variant;
+
+    public EvaluatedToggle(String name, boolean enabled, @Nullable Variant variant) {
+        this.enabled = enabled;
+        this.name = name;
+        this.variant = variant;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    @Nullable
+    public Variant getVariant() {
+        return variant;
+    }
+}
diff --git a/src/main/java/no/finn/unleash/FakeUnleash.java b/src/main/java/no/finn/unleash/FakeUnleash.java
index b007aaca..11a7f544 100644
--- a/src/main/java/no/finn/unleash/FakeUnleash.java
+++ b/src/main/java/no/finn/unleash/FakeUnleash.java
@@ -1,10 +1,13 @@
 package no.finn.unleash;
 
+import no.finn.unleash.lang.Nullable;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
+import java.util.stream.Collectors;
 
 public final class FakeUnleash implements Unleash {
     private boolean enableAll = false;
@@ -71,7 +74,12 @@ public final class FakeUnleash implements Unleash {
 
     @Override
     public List<String> getFeatureToggleNames() {
-        return new ArrayList<>(features.keySet());
+        return more().getFeatureToggleNames();
+    }
+
+    @Override
+    public MoreOperations more() {
+        return new FakeMore();
     }
 
     public void enableAll() {
@@ -114,4 +122,36 @@ public final class FakeUnleash implements Unleash {
     public void setVariant(String t1, Variant a) {
         variants.put(t1, a);
     }
+
+    public class FakeMore implements MoreOperations {
+
+        @Override
+        public List<String> getFeatureToggleNames() {
+            return new ArrayList<>(features.keySet());
+        }
+
+
+        @Override
+        public List<EvaluatedToggle> evaluateAllToggles() {
+            return evaluateAllToggles(null);
+        }
+
+        @Override
+        public List<EvaluatedToggle> evaluateAllToggles(@Nullable UnleashContext context) {
+            return getFeatureToggleNames().stream().map(toggleName -> {
+                return new EvaluatedToggle(toggleName, isEnabled(toggleName), getVariant(toggleName));
+            }).collect(Collectors.toList());
+        }
+
+        @Override
+        public void count(String toggleName, boolean enabled) {
+            //Nothing to count
+        }
+
+        @Override
+        public void countVariant(String toggleName, String variantName) {
+            //Nothing to count
+        }
+    }
+
 }
diff --git a/src/main/java/no/finn/unleash/FeatureToggle.java b/src/main/java/no/finn/unleash/FeatureToggle.java
index 1d6d13d1..41f6e649 100644
--- a/src/main/java/no/finn/unleash/FeatureToggle.java
+++ b/src/main/java/no/finn/unleash/FeatureToggle.java
@@ -4,7 +4,6 @@ import static java.util.Collections.emptyList;
 
 import java.util.Collections;
 import java.util.List;
-
 import no.finn.unleash.lang.Nullable;
 import no.finn.unleash.variant.VariantDefinition;
 
diff --git a/src/main/java/no/finn/unleash/MoreOperations.java b/src/main/java/no/finn/unleash/MoreOperations.java
new file mode 100644
index 00000000..f40ea5a2
--- /dev/null
+++ b/src/main/java/no/finn/unleash/MoreOperations.java
@@ -0,0 +1,22 @@
+package no.finn.unleash;
+
+import java.util.List;
+
+public interface MoreOperations {
+
+
+    List<String> getFeatureToggleNames();
+
+    List<EvaluatedToggle> evaluateAllToggles();
+
+    /**
+     * Evaluate all toggles using the provided context. This does not record the corresponding usage metrics for each toggle
+     * @param context
+     * @return
+     */
+    List<EvaluatedToggle> evaluateAllToggles(UnleashContext context);
+
+    void count(String toggleName, boolean enabled);
+
+    void countVariant(String toggleName, String variantName);
+}
diff --git a/src/main/java/no/finn/unleash/Unleash.java b/src/main/java/no/finn/unleash/Unleash.java
index bceba1fc..95f7a417 100644
--- a/src/main/java/no/finn/unleash/Unleash.java
+++ b/src/main/java/no/finn/unleash/Unleash.java
@@ -37,7 +37,14 @@ public interface Unleash {
 
     Variant getVariant(final String toggleName, final Variant defaultValue);
 
+    /**
+     * Use more().getFeatureToggleNames() instead
+     * @return a list of known toggle names
+     */
+    @Deprecated()
     List<String> getFeatureToggleNames();
 
     default void shutdown() {}
+
+    MoreOperations more();
 }
diff --git a/src/main/java/no/finn/unleash/UnleashContext.java b/src/main/java/no/finn/unleash/UnleashContext.java
index d85838d6..d5c0c4ab 100644
--- a/src/main/java/no/finn/unleash/UnleashContext.java
+++ b/src/main/java/no/finn/unleash/UnleashContext.java
@@ -3,7 +3,6 @@ package no.finn.unleash;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
-
 import no.finn.unleash.lang.Nullable;
 import no.finn.unleash.util.UnleashConfig;
 
diff --git a/src/main/java/no/finn/unleash/Variant.java b/src/main/java/no/finn/unleash/Variant.java
index 14d2de3b..dc31c629 100644
--- a/src/main/java/no/finn/unleash/Variant.java
+++ b/src/main/java/no/finn/unleash/Variant.java
@@ -2,7 +2,6 @@ package no.finn.unleash;
 
 import java.util.Objects;
 import java.util.Optional;
-
 import no.finn.unleash.lang.Nullable;
 import no.finn.unleash.variant.Payload;
 
@@ -13,7 +12,7 @@ public class Variant {
     @Nullable private final Payload payload;
     private final boolean enabled;
 
-    public Variant(String name, @Nullable  Payload payload, boolean enabled) {
+    public Variant(String name, @Nullable Payload payload, boolean enabled) {
         this.name = name;
         this.payload = payload;
         this.enabled = enabled;
diff --git a/src/main/java/no/finn/unleash/lang/NonNullApi.java b/src/main/java/no/finn/unleash/lang/NonNullApi.java
index 744f3583..2d0d6af5 100644
--- a/src/main/java/no/finn/unleash/lang/NonNullApi.java
+++ b/src/main/java/no/finn/unleash/lang/NonNullApi.java
@@ -1,14 +1,12 @@
 package no.finn.unleash.lang;
 
+import java.lang.annotation.*;
 import javax.annotation.Nonnull;
 import javax.annotation.meta.TypeQualifierDefault;
-import java.lang.annotation.*;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Nonnull
 @TypeQualifierDefault({ElementType.PARAMETER, ElementType.METHOD})
 @Target({ElementType.PACKAGE, ElementType.TYPE})
-public @interface NonNullApi {
-}
-
+public @interface NonNullApi {}
diff --git a/src/main/java/no/finn/unleash/lang/NonNullFields.java b/src/main/java/no/finn/unleash/lang/NonNullFields.java
index f60b55f4..57949b9e 100644
--- a/src/main/java/no/finn/unleash/lang/NonNullFields.java
+++ b/src/main/java/no/finn/unleash/lang/NonNullFields.java
@@ -1,14 +1,12 @@
 package no.finn.unleash.lang;
 
+import java.lang.annotation.*;
 import javax.annotation.Nonnull;
 import javax.annotation.meta.TypeQualifierDefault;
-import java.lang.annotation.*;
-
 
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Nonnull
 @TypeQualifierDefault(ElementType.FIELD)
 @Target({ElementType.PACKAGE, ElementType.TYPE})
-public @interface NonNullFields {
-}
+public @interface NonNullFields {}
diff --git a/src/main/java/no/finn/unleash/lang/Nullable.java b/src/main/java/no/finn/unleash/lang/Nullable.java
index 44c3747a..453d57ee 100644
--- a/src/main/java/no/finn/unleash/lang/Nullable.java
+++ b/src/main/java/no/finn/unleash/lang/Nullable.java
@@ -1,14 +1,13 @@
 package no.finn.unleash.lang;
 
+import java.lang.annotation.*;
 import javax.annotation.Nonnull;
 import javax.annotation.meta.TypeQualifierNickname;
 import javax.annotation.meta.When;
-import java.lang.annotation.*;
 
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Nonnull(when = When.MAYBE)
 @TypeQualifierNickname
 @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
-public @interface Nullable {
-}
+public @interface Nullable {}
diff --git a/src/main/java/no/finn/unleash/metric/MetricsBucket.java b/src/main/java/no/finn/unleash/metric/MetricsBucket.java
index 09194a0f..a3083239 100644
--- a/src/main/java/no/finn/unleash/metric/MetricsBucket.java
+++ b/src/main/java/no/finn/unleash/metric/MetricsBucket.java
@@ -1,12 +1,11 @@
 package no.finn.unleash.metric;
 
-import no.finn.unleash.lang.Nullable;
-
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import no.finn.unleash.lang.Nullable;
 
 class MetricsBucket {
     private final ConcurrentMap<String, ToggleCount> toggles;
diff --git a/src/main/java/no/finn/unleash/metric/UnleashMetricsSender.java b/src/main/java/no/finn/unleash/metric/UnleashMetricsSender.java
index da66bde2..c99824ee 100644
--- a/src/main/java/no/finn/unleash/metric/UnleashMetricsSender.java
+++ b/src/main/java/no/finn/unleash/metric/UnleashMetricsSender.java
@@ -46,7 +46,8 @@ public class UnleashMetricsSender {
                 Type type,
                 JsonSerializationContext jsonSerializationContext) {
             return new JsonPrimitive(ISO_INSTANT.format(localDateTime.toInstant(ZoneOffset.UTC)));
-        };
+        }
+        ;
     }
 
     static class AtomicLongSerializer implements JsonSerializer<AtomicLong> {
diff --git a/src/main/java/no/finn/unleash/metric/package-info.java b/src/main/java/no/finn/unleash/metric/package-info.java
index ab29fbc2..649d7282 100644
--- a/src/main/java/no/finn/unleash/metric/package-info.java
+++ b/src/main/java/no/finn/unleash/metric/package-info.java
@@ -1,6 +1,5 @@
 @NonNullApi
 @NonNullFields
-
 package no.finn.unleash.metric;
 
 import no.finn.unleash.lang.NonNullApi;
diff --git a/src/main/java/no/finn/unleash/package-info.java b/src/main/java/no/finn/unleash/package-info.java
index 8986cfc3..3aad9f27 100644
--- a/src/main/java/no/finn/unleash/package-info.java
+++ b/src/main/java/no/finn/unleash/package-info.java
@@ -1,6 +1,5 @@
 @NonNullApi
 @NonNullFields
-
 package no.finn.unleash;
 
 import no.finn.unleash.lang.NonNullApi;
diff --git a/src/main/java/no/finn/unleash/repository/JsonToggleCollectionDeserializer.java b/src/main/java/no/finn/unleash/repository/JsonToggleCollectionDeserializer.java
index c3f638dc..e49401e5 100644
--- a/src/main/java/no/finn/unleash/repository/JsonToggleCollectionDeserializer.java
+++ b/src/main/java/no/finn/unleash/repository/JsonToggleCollectionDeserializer.java
@@ -32,8 +32,7 @@ public class JsonToggleCollectionDeserializer implements JsonDeserializer<Toggle
         }
     }
 
-    static @Nullable
-    ToggleCollection deserializeVersion0(
+    static @Nullable ToggleCollection deserializeVersion0(
             JsonElement rootElement, JsonDeserializationContext context) {
         if (!rootElement.getAsJsonObject().has("features")) {
             return null;
diff --git a/src/main/java/no/finn/unleash/repository/ToggleRepository.java b/src/main/java/no/finn/unleash/repository/ToggleRepository.java
index c7a44446..3ac76c91 100644
--- a/src/main/java/no/finn/unleash/repository/ToggleRepository.java
+++ b/src/main/java/no/finn/unleash/repository/ToggleRepository.java
@@ -5,7 +5,8 @@ import no.finn.unleash.FeatureToggle;
 import no.finn.unleash.lang.Nullable;
 
 public interface ToggleRepository {
-    @Nullable FeatureToggle getToggle(String name);
+    @Nullable
+    FeatureToggle getToggle(String name);
 
     List<String> getFeatureNames();
 }
diff --git a/src/main/java/no/finn/unleash/repository/package-info.java b/src/main/java/no/finn/unleash/repository/package-info.java
index f063e4ac..6c1e2b71 100644
--- a/src/main/java/no/finn/unleash/repository/package-info.java
+++ b/src/main/java/no/finn/unleash/repository/package-info.java
@@ -1,6 +1,5 @@
 @NonNullApi
 @NonNullFields
-
 package no.finn.unleash.repository;
 
 import no.finn.unleash.lang.NonNullApi;
diff --git a/src/main/java/no/finn/unleash/strategy/package-info.java b/src/main/java/no/finn/unleash/strategy/package-info.java
index 551cf651..a0d5f7ed 100644
--- a/src/main/java/no/finn/unleash/strategy/package-info.java
+++ b/src/main/java/no/finn/unleash/strategy/package-info.java
@@ -1,6 +1,5 @@
 @NonNullFields
 @NonNullApi
-
 package no.finn.unleash.strategy;
 
 import no.finn.unleash.lang.NonNullApi;
diff --git a/src/main/java/no/finn/unleash/util/IpAddressMatcher.java b/src/main/java/no/finn/unleash/util/IpAddressMatcher.java
index bcf856e9..a068341b 100644
--- a/src/main/java/no/finn/unleash/util/IpAddressMatcher.java
+++ b/src/main/java/no/finn/unleash/util/IpAddressMatcher.java
@@ -15,12 +15,11 @@
  */
 package no.finn.unleash.util;
 
-import no.finn.unleash.lang.Nullable;
-
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
 import java.util.regex.Pattern;
+import no.finn.unleash.lang.Nullable;
 
 /**
  * Matches a request based on IP Address or subnet mask matching against the remote address.
diff --git a/src/main/java/no/finn/unleash/util/UnleashScheduledExecutor.java b/src/main/java/no/finn/unleash/util/UnleashScheduledExecutor.java
index 72288450..1b0fd300 100644
--- a/src/main/java/no/finn/unleash/util/UnleashScheduledExecutor.java
+++ b/src/main/java/no/finn/unleash/util/UnleashScheduledExecutor.java
@@ -1,10 +1,9 @@
 package no.finn.unleash.util;
 
-import no.finn.unleash.lang.Nullable;
-
 import java.util.concurrent.Future;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.ScheduledFuture;
+import no.finn.unleash.lang.Nullable;
 
 public interface UnleashScheduledExecutor {
     @Nullable
diff --git a/src/main/java/no/finn/unleash/util/UnleashScheduledExecutorImpl.java b/src/main/java/no/finn/unleash/util/UnleashScheduledExecutorImpl.java
index f01e7795..bba76ec2 100644
--- a/src/main/java/no/finn/unleash/util/UnleashScheduledExecutorImpl.java
+++ b/src/main/java/no/finn/unleash/util/UnleashScheduledExecutorImpl.java
@@ -1,7 +1,6 @@
 package no.finn.unleash.util;
 
 import java.util.concurrent.*;
-
 import no.finn.unleash.lang.Nullable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -10,8 +9,7 @@ public class UnleashScheduledExecutorImpl implements UnleashScheduledExecutor {
 
     private static final Logger LOG = LoggerFactory.getLogger(UnleashScheduledExecutorImpl.class);
 
-    @Nullable
-    private static UnleashScheduledExecutorImpl INSTANCE;
+    @Nullable private static UnleashScheduledExecutorImpl INSTANCE;
 
     private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
     private final ExecutorService executorService;
@@ -39,7 +37,8 @@ public class UnleashScheduledExecutorImpl implements UnleashScheduledExecutor {
     }
 
     @Override
-    public @Nullable ScheduledFuture setInterval(Runnable command, long initialDelaySec, long periodSec) {
+    public @Nullable ScheduledFuture setInterval(
+            Runnable command, long initialDelaySec, long periodSec) {
         try {
             return scheduledThreadPoolExecutor.scheduleAtFixedRate(
                     command, initialDelaySec, periodSec, TimeUnit.SECONDS);
diff --git a/src/main/java/no/finn/unleash/util/package-info.java b/src/main/java/no/finn/unleash/util/package-info.java
index 2f657c81..49bb5e80 100644
--- a/src/main/java/no/finn/unleash/util/package-info.java
+++ b/src/main/java/no/finn/unleash/util/package-info.java
@@ -1,6 +1,5 @@
 @NonNullApi
 @NonNullFields
-
 package no.finn.unleash.util;
 
 import no.finn.unleash.lang.NonNullApi;
diff --git a/src/main/java/no/finn/unleash/variant/Payload.java b/src/main/java/no/finn/unleash/variant/Payload.java
index 9bab085f..699f58c3 100644
--- a/src/main/java/no/finn/unleash/variant/Payload.java
+++ b/src/main/java/no/finn/unleash/variant/Payload.java
@@ -1,14 +1,13 @@
 package no.finn.unleash.variant;
 
-import no.finn.unleash.lang.Nullable;
-
 import java.util.Objects;
+import no.finn.unleash.lang.Nullable;
 
 public class Payload {
     private String type;
     @Nullable private String value;
 
-    public Payload(String type,@Nullable String value) {
+    public Payload(String type, @Nullable String value) {
         this.type = type;
         this.value = value;
     }
@@ -33,4 +32,12 @@ public class Payload {
     public int hashCode() {
         return Objects.hash(type, value);
     }
+
+    @Override
+    public String toString() {
+        return "Payload{" +
+            "type='" + type + '\'' +
+            ", value='" + value + '\'' +
+            '}';
+    }
 }
diff --git a/src/main/java/no/finn/unleash/variant/VariantDefinition.java b/src/main/java/no/finn/unleash/variant/VariantDefinition.java
index 3a31d91d..9b3890e3 100644
--- a/src/main/java/no/finn/unleash/variant/VariantDefinition.java
+++ b/src/main/java/no/finn/unleash/variant/VariantDefinition.java
@@ -13,7 +13,10 @@ public class VariantDefinition {
     @Nullable private final List<VariantOverride> overrides;
 
     public VariantDefinition(
-        String name, int weight, @Nullable Payload payload, @Nullable List<VariantOverride> overrides) {
+            String name,
+            int weight,
+            @Nullable Payload payload,
+            @Nullable List<VariantOverride> overrides) {
         this.name = name;
         this.weight = weight;
         this.payload = payload;
diff --git a/src/main/java/no/finn/unleash/variant/VariantUtil.java b/src/main/java/no/finn/unleash/variant/VariantUtil.java
index 45d4dfae..483200f6 100644
--- a/src/main/java/no/finn/unleash/variant/VariantUtil.java
+++ b/src/main/java/no/finn/unleash/variant/VariantUtil.java
@@ -62,8 +62,8 @@ public final class VariantUtil {
     }
 
     public static Variant selectVariant(
-        @Nullable FeatureToggle featureToggle, UnleashContext context, Variant defaultVariant) {
-        if(featureToggle == null) {
+            @Nullable FeatureToggle featureToggle, UnleashContext context, Variant defaultVariant) {
+        if (featureToggle == null) {
             return defaultVariant;
         }
         List<VariantDefinition> variants = featureToggle.getVariants();
diff --git a/src/main/java/no/finn/unleash/variant/package-info.java b/src/main/java/no/finn/unleash/variant/package-info.java
index 68776247..a3fc381d 100644
--- a/src/main/java/no/finn/unleash/variant/package-info.java
+++ b/src/main/java/no/finn/unleash/variant/package-info.java
@@ -1,6 +1,5 @@
 @NonNullFields
 @NonNullApi
-
 package no.finn.unleash.variant;
 
 import no.finn.unleash.lang.NonNullApi;


Test patch:
diff --git a/src/test/java/no/finn/unleash/DefaultUnleashTest.java b/src/test/java/no/finn/unleash/DefaultUnleashTest.java
new file mode 100644
index 00000000..e66d6df2
--- /dev/null
+++ b/src/test/java/no/finn/unleash/DefaultUnleashTest.java
@@ -0,0 +1,66 @@
+package no.finn.unleash;
+
+import no.finn.unleash.event.EventDispatcher;
+import no.finn.unleash.metric.UnleashMetricService;
+import no.finn.unleash.repository.ToggleRepository;
+import no.finn.unleash.strategy.Strategy;
+import no.finn.unleash.util.UnleashConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class DefaultUnleashTest {
+    private DefaultUnleash sut;
+    private ToggleRepository toggleRepository;
+    private UnleashContextProvider contextProvider;
+    private EventDispatcher eventDispatcher;
+    private UnleashMetricService metricService;
+
+    @BeforeEach
+    public void setup() {
+        UnleashConfig unleashConfig = UnleashConfig.builder().unleashAPI("http://fakeAPI").appName("fakeApp").build();
+        toggleRepository = mock(ToggleRepository.class);
+        Map<String, Strategy> strategyMap = new HashMap<>();
+        contextProvider = mock(UnleashContextProvider.class);
+        eventDispatcher = mock(EventDispatcher.class);
+        metricService = mock(UnleashMetricService.class);
+
+        sut = new DefaultUnleash(unleashConfig, toggleRepository, strategyMap, contextProvider, eventDispatcher, metricService);
+    }
+
+    @Test
+    public void should_evaluate_all_toggle_with_context() {
+        when(toggleRepository.getFeatureNames()).thenReturn(Arrays.asList("toggle1", "toggle2"));
+        when(contextProvider.getContext()).thenReturn(UnleashContext.builder().build());
+
+        List<EvaluatedToggle> toggles = sut.more().evaluateAllToggles();
+        assertThat(toggles).hasSize(2);
+        EvaluatedToggle t1 = toggles.get(0);
+        assertThat(t1.getName()).isEqualTo("toggle1");
+        assertThat(t1.isEnabled()).isFalse();
+    }
+
+    @Test
+    public void should_count_and_not_throw_an_error() {
+        sut.more().count("toggle1", true);
+        sut.more().count("toggle1", false);
+
+        verify(metricService).count("toggle1", true);
+        verify(metricService).count("toggle1", false);
+    }
+
+    @Test
+    public void should_countVariant_and_not_throw_an_error() {
+        sut.more().countVariant("toggle1", "variant1");
+
+        verify(metricService).countVariant("toggle1", "variant1");
+    }
+}
diff --git a/src/test/java/no/finn/unleash/FakeUnleashTest.java b/src/test/java/no/finn/unleash/FakeUnleashTest.java
index e844f602..b9bed243 100644
--- a/src/test/java/no/finn/unleash/FakeUnleashTest.java
+++ b/src/test/java/no/finn/unleash/FakeUnleashTest.java
@@ -91,6 +91,16 @@ public class FakeUnleashTest {
         assertThat(fakeUnleash.getFeatureToggleNames()).containsAll(expected);
     }
 
+    @Test
+    public void should_get_all_feature_names_via_more() {
+        FakeUnleash fakeUnleash = new FakeUnleash();
+        fakeUnleash.enable("t1", "t2");
+        fakeUnleash.disable("t2");
+
+        List<String> expected = Arrays.asList(new String[] {"t1", "t2"});
+        assertThat(fakeUnleash.more().getFeatureToggleNames()).containsAll(expected);
+    }
+
     @Test
     public void should_get_variant() {
         FakeUnleash fakeUnleash = new FakeUnleash();
@@ -100,6 +110,32 @@ public class FakeUnleashTest {
         assertThat(fakeUnleash.getVariant("t1").getName()).isEqualTo("a");
     }
 
+    @Test
+    public void should_evaluate_all_toggle_without_context() {
+        FakeUnleash fakeUnleash = new FakeUnleash();
+        fakeUnleash.enable("t1", "t2");
+        fakeUnleash.setVariant("t1", new Variant("a", (String) null, true));
+
+        List<EvaluatedToggle> toggles = fakeUnleash.more().evaluateAllToggles();
+        assertThat(toggles).hasSize(2);
+        EvaluatedToggle t1 = toggles.get(0);
+        assertThat(t1.getName()).isEqualTo("t1");
+        assertThat(t1.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void should_evaluate_all_toggle_with_context() {
+        FakeUnleash fakeUnleash = new FakeUnleash();
+        fakeUnleash.enable("t1", "t2");
+        fakeUnleash.setVariant("t1", new Variant("a", (String) null, true));
+
+        List<EvaluatedToggle> toggles = fakeUnleash.more().evaluateAllToggles(new UnleashContext.Builder().build());
+        assertThat(toggles).hasSize(2);
+        EvaluatedToggle t1 = toggles.get(0);
+        assertThat(t1.getName()).isEqualTo("t1");
+        assertThat(t1.isEnabled()).isTrue();
+    }
+
     @Test
     public void should_get_disabled_variant_when_toggle_is_disabled() {
         FakeUnleash fakeUnleash = new FakeUnleash();
@@ -108,4 +144,16 @@ public class FakeUnleashTest {
 
         assertThat(fakeUnleash.getVariant("t1").getName()).isEqualTo("disabled");
     }
+
+    @Test
+    public void should_count_and_not_throw_an_error() {
+        FakeUnleash fakeUnleash = new FakeUnleash();
+        fakeUnleash.more().count("anything", true);
+    }
+
+    @Test
+    public void should_countVariant_and_not_throw_an_error() {
+        FakeUnleash fakeUnleash = new FakeUnleash();
+        fakeUnleash.more().countVariant("toggleName", "variantName");
+    }
 }