added support for onChange report

Report is now notified when metric changes and updates reading values.

Tested:
  - Added new unit tests
  - OnChange report updates Readings when metric values changes

Change-Id: I3be9ef7aa0486cb15bac627aa1de5cc632613b3b
Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
diff --git a/meson.build b/meson.build
index 138dba5..d1e228d 100644
--- a/meson.build
+++ b/meson.build
@@ -75,10 +75,11 @@
 executable(
     'telemetry',
     [
-        'src/details/collection_function.cpp',
         'src/discrete_threshold.cpp',
         'src/main.cpp',
         'src/metric.cpp',
+        'src/metrics/collection_data.cpp',
+        'src/metrics/collection_function.cpp',
         'src/numeric_threshold.cpp',
         'src/on_change_threshold.cpp',
         'src/persistent_json_storage.cpp',
diff --git a/src/discrete_threshold.cpp b/src/discrete_threshold.cpp
index a2073b7..d7640f6 100644
--- a/src/discrete_threshold.cpp
+++ b/src/discrete_threshold.cpp
@@ -40,10 +40,6 @@
 }
 
 void DiscreteThreshold::sensorUpdated(interfaces::Sensor& sensor,
-                                      Milliseconds timestamp)
-{}
-
-void DiscreteThreshold::sensorUpdated(interfaces::Sensor& sensor,
                                       Milliseconds timestamp, double value)
 {
     auto& details = getDetails(sensor);
diff --git a/src/discrete_threshold.hpp b/src/discrete_threshold.hpp
index 1f145f4..2a51a1a 100644
--- a/src/discrete_threshold.hpp
+++ b/src/discrete_threshold.hpp
@@ -30,7 +30,6 @@
     DiscreteThreshold(DiscreteThreshold&&) = delete;
 
     void initialize() override;
-    void sensorUpdated(interfaces::Sensor&, Milliseconds) override;
     void sensorUpdated(interfaces::Sensor&, Milliseconds, double) override;
     LabeledThresholdParam getThresholdParam() const override;
     void updateSensors(Sensors newSensors) override;
diff --git a/src/interfaces/metric.hpp b/src/interfaces/metric.hpp
index 4e2fae3..c40e960 100644
--- a/src/interfaces/metric.hpp
+++ b/src/interfaces/metric.hpp
@@ -1,6 +1,8 @@
 #pragma once
 
+#include "interfaces/metric_listener.hpp"
 #include "metric_value.hpp"
+#include "types/duration_types.hpp"
 #include "types/report_types.hpp"
 
 #include <nlohmann/json.hpp>
@@ -20,6 +22,11 @@
     virtual std::vector<MetricValue> getReadings() const = 0;
     virtual LabeledMetricParameters dumpConfiguration() const = 0;
     virtual uint64_t sensorCount() const = 0;
+    virtual void registerForUpdates(interfaces::MetricListener& listener) = 0;
+    virtual void
+        unregisterFromUpdates(interfaces::MetricListener& listener) = 0;
+    virtual void updateReadings(Milliseconds) = 0;
+    virtual bool isTimerRequired() const = 0;
 };
 
 } // namespace interfaces
diff --git a/src/interfaces/metric_listener.hpp b/src/interfaces/metric_listener.hpp
new file mode 100644
index 0000000..3006a09
--- /dev/null
+++ b/src/interfaces/metric_listener.hpp
@@ -0,0 +1,14 @@
+#pragma once
+
+namespace interfaces
+{
+
+class MetricListener
+{
+  public:
+    virtual ~MetricListener() = default;
+
+    virtual void metricUpdated() = 0;
+};
+
+} // namespace interfaces
diff --git a/src/interfaces/sensor_listener.hpp b/src/interfaces/sensor_listener.hpp
index a47c8d4..3f35c38 100644
--- a/src/interfaces/sensor_listener.hpp
+++ b/src/interfaces/sensor_listener.hpp
@@ -15,7 +15,6 @@
   public:
     virtual ~SensorListener() = default;
 
-    virtual void sensorUpdated(interfaces::Sensor&, Milliseconds) = 0;
     virtual void sensorUpdated(interfaces::Sensor&, Milliseconds, double) = 0;
 };
 
diff --git a/src/metric.cpp b/src/metric.cpp
index 3e450d6..a9cf45e 100644
--- a/src/metric.cpp
+++ b/src/metric.cpp
@@ -1,6 +1,6 @@
 #include "metric.hpp"
 
-#include "details/collection_function.hpp"
+#include "metrics/collection_data.hpp"
 #include "types/report_types.hpp"
 #include "types/sensor_types.hpp"
 #include "utils/labeled_tuple.hpp"
@@ -10,130 +10,6 @@
 
 #include <algorithm>
 
-class Metric::CollectionData
-{
-  public:
-    using ReadingItem = details::ReadingItem;
-
-    virtual ~CollectionData() = default;
-
-    virtual std::optional<double> update(Milliseconds timestamp) = 0;
-    virtual double update(Milliseconds timestamp, double value) = 0;
-};
-
-class Metric::DataPoint : public Metric::CollectionData
-{
-  public:
-    std::optional<double> update(Milliseconds) override
-    {
-        return lastReading;
-    }
-
-    double update(Milliseconds, double reading) override
-    {
-        lastReading = reading;
-        return reading;
-    }
-
-  private:
-    std::optional<double> lastReading;
-};
-
-class Metric::DataInterval : public Metric::CollectionData
-{
-  public:
-    DataInterval(std::shared_ptr<details::CollectionFunction> function,
-                 CollectionDuration duration) :
-        function(std::move(function)),
-        duration(duration)
-    {
-        if (duration.t.count() == 0)
-        {
-            throw sdbusplus::exception::SdBusError(
-                static_cast<int>(std::errc::invalid_argument),
-                "Invalid CollectionDuration");
-        }
-    }
-
-    std::optional<double> update(Milliseconds timestamp) override
-    {
-        if (readings.empty())
-        {
-            return std::nullopt;
-        }
-
-        cleanup(timestamp);
-
-        return function->calculate(readings, timestamp);
-    }
-
-    double update(Milliseconds timestamp, double reading) override
-    {
-        readings.emplace_back(timestamp, reading);
-
-        cleanup(timestamp);
-
-        return function->calculate(readings, timestamp);
-    }
-
-  private:
-    void cleanup(Milliseconds timestamp)
-    {
-        auto it = readings.begin();
-        for (auto kt = std::next(readings.rbegin()); kt != readings.rend();
-             ++kt)
-        {
-            const auto& [nextItemTimestamp, nextItemReading] = *std::prev(kt);
-            if (timestamp >= nextItemTimestamp &&
-                timestamp - nextItemTimestamp > duration.t)
-            {
-                it = kt.base();
-                break;
-            }
-        }
-        readings.erase(readings.begin(), it);
-
-        if (timestamp > duration.t)
-        {
-            readings.front().first =
-                std::max(readings.front().first, timestamp - duration.t);
-        }
-    }
-
-    std::shared_ptr<details::CollectionFunction> function;
-    std::vector<ReadingItem> readings;
-    CollectionDuration duration;
-};
-
-class Metric::DataStartup : public Metric::CollectionData
-{
-  public:
-    explicit DataStartup(
-        std::shared_ptr<details::CollectionFunction> function) :
-        function(std::move(function))
-    {}
-
-    std::optional<double> update(Milliseconds timestamp) override
-    {
-        if (readings.empty())
-        {
-            return std::nullopt;
-        }
-
-        return function->calculateForStartupInterval(readings, timestamp);
-    }
-
-    double update(Milliseconds timestamp, double reading) override
-    {
-        readings.emplace_back(timestamp, reading);
-        return function->calculateForStartupInterval(readings, timestamp);
-    }
-
-  private:
-    std::shared_ptr<details::CollectionFunction> function;
-    std::vector<ReadingItem> readings;
-};
-
 Metric::Metric(Sensors sensorsIn, OperationType operationTypeIn,
                std::string idIn, CollectionTimeScope timeScopeIn,
                CollectionDuration collectionDurationIn,
@@ -141,9 +17,9 @@
     id(std::move(idIn)),
     sensors(std::move(sensorsIn)), operationType(operationTypeIn),
     collectionTimeScope(timeScopeIn), collectionDuration(collectionDurationIn),
-    collectionAlgorithms(makeCollectionData(sensors.size(), operationType,
-                                            collectionTimeScope,
-                                            collectionDuration)),
+    collectionAlgorithms(
+        metrics::makeCollectionData(sensors.size(), operationType,
+                                    collectionTimeScope, collectionDuration)),
     clock(std::move(clockIn))
 {
     readings = utils::transform(sensors, [this](const auto& sensor) {
@@ -151,7 +27,20 @@
     });
 }
 
-Metric::~Metric() = default;
+void Metric::registerForUpdates(interfaces::MetricListener& listener)
+{
+    listeners.emplace_back(listener);
+}
+
+void Metric::unregisterFromUpdates(interfaces::MetricListener& listener)
+{
+    listeners.erase(
+        std::remove_if(listeners.begin(), listeners.end(),
+                       [&listener](const interfaces::MetricListener& item) {
+                           return &item == &listener;
+                       }),
+        listeners.end());
+}
 
 void Metric::initialize()
 {
@@ -190,18 +79,22 @@
     return resultReadings;
 }
 
-void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp)
-{
-    findAssociatedData(notifier).update(timestamp);
-}
-
 void Metric::sensorUpdated(interfaces::Sensor& notifier, Milliseconds timestamp,
                            double value)
 {
-    findAssociatedData(notifier).update(timestamp, value);
+    auto& data = findAssociatedData(notifier);
+    double newValue = data.update(timestamp, value);
+
+    if (data.updateLastValue(newValue))
+    {
+        for (interfaces::MetricListener& listener : listeners)
+        {
+            listener.metricUpdated();
+        }
+    }
 }
 
-Metric::CollectionData&
+metrics::CollectionData&
     Metric::findAssociatedData(const interfaces::Sensor& notifier)
 {
     auto it = std::find_if(
@@ -222,46 +115,42 @@
                                    collectionTimeScope, collectionDuration);
 }
 
-std::vector<std::unique_ptr<Metric::CollectionData>>
-    Metric::makeCollectionData(size_t size, OperationType op,
-                               CollectionTimeScope timeScope,
-                               CollectionDuration duration)
-{
-    using namespace std::string_literals;
-
-    std::vector<std::unique_ptr<Metric::CollectionData>> result;
-
-    result.reserve(size);
-
-    switch (timeScope)
-    {
-        case CollectionTimeScope::interval:
-            std::generate_n(
-                std::back_inserter(result), size,
-                [cf = details::makeCollectionFunction(op), duration] {
-                    return std::make_unique<DataInterval>(cf, duration);
-                });
-            break;
-        case CollectionTimeScope::point:
-            std::generate_n(std::back_inserter(result), size,
-                            [] { return std::make_unique<DataPoint>(); });
-            break;
-        case CollectionTimeScope::startup:
-            std::generate_n(std::back_inserter(result), size,
-                            [cf = details::makeCollectionFunction(op)] {
-                                return std::make_unique<DataStartup>(cf);
-                            });
-            break;
-        default:
-            throw std::runtime_error("timeScope: "s +
-                                     utils::enumToString(timeScope) +
-                                     " is not supported"s);
-    }
-
-    return result;
-}
-
 uint64_t Metric::sensorCount() const
 {
     return sensors.size();
 }
+
+void Metric::updateReadings(Milliseconds timestamp)
+{
+    for (auto& data : collectionAlgorithms)
+    {
+        if (std::optional<double> newValue = data->update(timestamp))
+        {
+            if (data->updateLastValue(*newValue))
+            {
+                for (interfaces::MetricListener& listener : listeners)
+                {
+                    listener.metricUpdated();
+                }
+                return;
+            }
+        }
+    }
+}
+
+bool Metric::isTimerRequired() const
+{
+    if (collectionTimeScope == CollectionTimeScope::point)
+    {
+        return false;
+    }
+
+    if (collectionTimeScope == CollectionTimeScope::startup &&
+        (operationType == OperationType::min ||
+         operationType == OperationType::max))
+    {
+        return false;
+    }
+
+    return true;
+}
diff --git a/src/metric.hpp b/src/metric.hpp
index 859315e..b627b4e 100644
--- a/src/metric.hpp
+++ b/src/metric.hpp
@@ -2,8 +2,11 @@
 
 #include "interfaces/clock.hpp"
 #include "interfaces/metric.hpp"
+#include "interfaces/metric_listener.hpp"
 #include "interfaces/sensor.hpp"
 #include "interfaces/sensor_listener.hpp"
+#include "metrics/collection_data.hpp"
+#include "types/collection_duration.hpp"
 
 class Metric :
     public interfaces::Metric,
@@ -14,28 +17,22 @@
     Metric(Sensors sensors, OperationType operationType, std::string id,
            CollectionTimeScope, CollectionDuration,
            std::unique_ptr<interfaces::Clock>);
-    ~Metric();
 
     void initialize() override;
     void deinitialize() override;
     std::vector<MetricValue> getReadings() const override;
-    void sensorUpdated(interfaces::Sensor&, Milliseconds) override;
     void sensorUpdated(interfaces::Sensor&, Milliseconds,
                        double value) override;
     LabeledMetricParameters dumpConfiguration() const override;
     uint64_t sensorCount() const override;
+    void registerForUpdates(interfaces::MetricListener& listener) override;
+    void unregisterFromUpdates(interfaces::MetricListener& listener) override;
+    void updateReadings(Milliseconds) override;
+    bool isTimerRequired() const override;
 
   private:
-    class CollectionData;
-    class DataPoint;
-    class DataInterval;
-    class DataStartup;
-
-    static std::vector<std::unique_ptr<CollectionData>>
-        makeCollectionData(size_t size, OperationType, CollectionTimeScope,
-                           CollectionDuration);
-
-    CollectionData& findAssociatedData(const interfaces::Sensor& notifier);
+    metrics::CollectionData&
+        findAssociatedData(const interfaces::Sensor& notifier);
 
     std::string id;
     std::vector<MetricValue> readings;
@@ -43,6 +40,7 @@
     OperationType operationType;
     CollectionTimeScope collectionTimeScope;
     CollectionDuration collectionDuration;
-    std::vector<std::unique_ptr<CollectionData>> collectionAlgorithms;
+    std::vector<std::unique_ptr<metrics::CollectionData>> collectionAlgorithms;
     std::unique_ptr<interfaces::Clock> clock;
+    std::vector<std::reference_wrapper<interfaces::MetricListener>> listeners;
 };
diff --git a/src/metrics/collection_data.cpp b/src/metrics/collection_data.cpp
new file mode 100644
index 0000000..9512252
--- /dev/null
+++ b/src/metrics/collection_data.cpp
@@ -0,0 +1,162 @@
+#include "metrics/collection_data.hpp"
+
+#include "metrics/collection_function.hpp"
+
+namespace metrics
+{
+
+bool CollectionData::updateLastValue(double value)
+{
+    const bool changed = lastValue != value;
+    lastValue = value;
+    return changed;
+}
+
+class DataPoint : public CollectionData
+{
+  public:
+    std::optional<double> update(Milliseconds) override
+    {
+        return lastReading;
+    }
+
+    double update(Milliseconds, double reading) override
+    {
+        lastReading = reading;
+        return reading;
+    }
+
+  private:
+    std::optional<double> lastReading;
+};
+
+class DataInterval : public CollectionData
+{
+  public:
+    DataInterval(std::shared_ptr<CollectionFunction> function,
+                 CollectionDuration duration) :
+        function(std::move(function)),
+        duration(duration)
+    {
+        if (duration.t.count() == 0)
+        {
+            throw sdbusplus::exception::SdBusError(
+                static_cast<int>(std::errc::invalid_argument),
+                "Invalid CollectionDuration");
+        }
+    }
+
+    std::optional<double> update(Milliseconds timestamp) override
+    {
+        if (readings.empty())
+        {
+            return std::nullopt;
+        }
+
+        cleanup(timestamp);
+
+        return function->calculate(readings, timestamp);
+    }
+
+    double update(Milliseconds timestamp, double reading) override
+    {
+        readings.emplace_back(timestamp, reading);
+
+        cleanup(timestamp);
+
+        return function->calculate(readings, timestamp);
+    }
+
+  private:
+    void cleanup(Milliseconds timestamp)
+    {
+        auto it = readings.begin();
+        for (auto kt = std::next(readings.rbegin()); kt != readings.rend();
+             ++kt)
+        {
+            const auto& [nextItemTimestamp, nextItemReading] = *std::prev(kt);
+            if (timestamp >= nextItemTimestamp &&
+                timestamp - nextItemTimestamp > duration.t)
+            {
+                it = kt.base();
+                break;
+            }
+        }
+        readings.erase(readings.begin(), it);
+
+        if (timestamp > duration.t)
+        {
+            readings.front().first =
+                std::max(readings.front().first, timestamp - duration.t);
+        }
+    }
+
+    std::shared_ptr<CollectionFunction> function;
+    std::vector<ReadingItem> readings;
+    CollectionDuration duration;
+};
+
+class DataStartup : public CollectionData
+{
+  public:
+    explicit DataStartup(std::shared_ptr<CollectionFunction> function) :
+        function(std::move(function))
+    {}
+
+    std::optional<double> update(Milliseconds timestamp) override
+    {
+        if (readings.empty())
+        {
+            return std::nullopt;
+        }
+
+        return function->calculateForStartupInterval(readings, timestamp);
+    }
+
+    double update(Milliseconds timestamp, double reading) override
+    {
+        readings.emplace_back(timestamp, reading);
+        return function->calculateForStartupInterval(readings, timestamp);
+    }
+
+  private:
+    std::shared_ptr<CollectionFunction> function;
+    std::vector<ReadingItem> readings;
+};
+
+std::vector<std::unique_ptr<CollectionData>>
+    makeCollectionData(size_t size, OperationType op,
+                       CollectionTimeScope timeScope,
+                       CollectionDuration duration)
+{
+    using namespace std::string_literals;
+
+    std::vector<std::unique_ptr<CollectionData>> result;
+
+    result.reserve(size);
+
+    switch (timeScope)
+    {
+        case CollectionTimeScope::interval:
+            std::generate_n(std::back_inserter(result), size,
+                            [cf = makeCollectionFunction(op), duration] {
+                                return std::make_unique<DataInterval>(cf,
+                                                                      duration);
+                            });
+            break;
+        case CollectionTimeScope::point:
+            std::generate_n(std::back_inserter(result), size,
+                            [] { return std::make_unique<DataPoint>(); });
+            break;
+        case CollectionTimeScope::startup:
+            std::generate_n(std::back_inserter(result), size,
+                            [cf = makeCollectionFunction(op)] {
+                                return std::make_unique<DataStartup>(cf);
+                            });
+            break;
+    }
+
+    return result;
+}
+
+} // namespace metrics
diff --git a/src/metrics/collection_data.hpp b/src/metrics/collection_data.hpp
new file mode 100644
index 0000000..251e704
--- /dev/null
+++ b/src/metrics/collection_data.hpp
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "types/collection_duration.hpp"
+#include "types/collection_time_scope.hpp"
+#include "types/duration_types.hpp"
+#include "types/operation_type.hpp"
+
+#include <memory>
+#include <optional>
+#include <vector>
+
+namespace metrics
+{
+
+class CollectionData
+{
+  public:
+    virtual ~CollectionData() = default;
+
+    virtual std::optional<double> update(Milliseconds timestamp) = 0;
+    virtual double update(Milliseconds timestamp, double value) = 0;
+    bool updateLastValue(double value);
+
+  private:
+    std::optional<double> lastValue;
+};
+
+std::vector<std::unique_ptr<CollectionData>>
+    makeCollectionData(size_t size, OperationType, CollectionTimeScope,
+                       CollectionDuration);
+
+} // namespace metrics
diff --git a/src/details/collection_function.cpp b/src/metrics/collection_function.cpp
similarity index 98%
rename from src/details/collection_function.cpp
rename to src/metrics/collection_function.cpp
index 5134b0e..717bb68 100644
--- a/src/details/collection_function.cpp
+++ b/src/metrics/collection_function.cpp
@@ -1,8 +1,8 @@
-#include "collection_function.hpp"
+#include "metrics/collection_function.hpp"
 
 #include <cmath>
 
-namespace details
+namespace metrics
 {
 
 class FunctionMinimum : public CollectionFunction
@@ -173,4 +173,4 @@
     }
 }
 
-} // namespace details
+} // namespace metrics
diff --git a/src/details/collection_function.hpp b/src/metrics/collection_function.hpp
similarity index 93%
rename from src/details/collection_function.hpp
rename to src/metrics/collection_function.hpp
index ca653a1..610f015 100644
--- a/src/details/collection_function.hpp
+++ b/src/metrics/collection_function.hpp
@@ -8,7 +8,7 @@
 #include <utility>
 #include <vector>
 
-namespace details
+namespace metrics
 {
 
 using ReadingItem = std::pair<Milliseconds, double>;
@@ -27,4 +27,4 @@
 
 std::shared_ptr<CollectionFunction> makeCollectionFunction(OperationType);
 
-} // namespace details
+} // namespace metrics
diff --git a/src/numeric_threshold.cpp b/src/numeric_threshold.cpp
index f152e76..cb6dbdd 100644
--- a/src/numeric_threshold.cpp
+++ b/src/numeric_threshold.cpp
@@ -41,10 +41,6 @@
 }
 
 void NumericThreshold::sensorUpdated(interfaces::Sensor& sensor,
-                                     Milliseconds timestamp)
-{}
-
-void NumericThreshold::sensorUpdated(interfaces::Sensor& sensor,
                                      Milliseconds timestamp, double value)
 {
     auto& details = getDetails(sensor);
diff --git a/src/numeric_threshold.hpp b/src/numeric_threshold.hpp
index 02480a4..ecf3d6a 100644
--- a/src/numeric_threshold.hpp
+++ b/src/numeric_threshold.hpp
@@ -30,7 +30,6 @@
     {}
 
     void initialize() override;
-    void sensorUpdated(interfaces::Sensor&, Milliseconds) override;
     void sensorUpdated(interfaces::Sensor&, Milliseconds, double) override;
     LabeledThresholdParam getThresholdParam() const override;
     void updateSensors(Sensors newSensors) override;
diff --git a/src/on_change_threshold.cpp b/src/on_change_threshold.cpp
index fe3d1dd..72aedc5 100644
--- a/src/on_change_threshold.cpp
+++ b/src/on_change_threshold.cpp
@@ -51,10 +51,6 @@
 }
 
 void OnChangeThreshold::sensorUpdated(interfaces::Sensor& sensor,
-                                      Milliseconds timestamp)
-{}
-
-void OnChangeThreshold::sensorUpdated(interfaces::Sensor& sensor,
                                       Milliseconds timestamp, double value)
 {
     commit(sensor.getName(), timestamp, value);
diff --git a/src/on_change_threshold.hpp b/src/on_change_threshold.hpp
index 32a5f65..80876a2 100644
--- a/src/on_change_threshold.hpp
+++ b/src/on_change_threshold.hpp
@@ -25,7 +25,6 @@
     {}
 
     void initialize() override;
-    void sensorUpdated(interfaces::Sensor&, Milliseconds) override;
     void sensorUpdated(interfaces::Sensor&, Milliseconds, double) override;
     LabeledThresholdParam getThresholdParam() const override;
     void updateSensors(Sensors newSensors) override;
diff --git a/src/report.cpp b/src/report.cpp
index 12d88c7..d9d89b8 100644
--- a/src/report.cpp
+++ b/src/report.cpp
@@ -6,6 +6,7 @@
 #include "report_manager.hpp"
 #include "utils/clock.hpp"
 #include "utils/contains.hpp"
+#include "utils/ensure.hpp"
 #include "utils/transform.hpp"
 
 #include <phosphor-logging/log.hpp>
@@ -13,6 +14,7 @@
 
 #include <limits>
 #include <numeric>
+#include <optional>
 
 Report::Report(boost::asio::io_context& ioc,
                const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
@@ -71,10 +73,7 @@
     persistency = storeConfiguration();
     reportIface = makeReportInterface(reportFactory);
 
-    if (reportingType == ReportingType::periodic)
-    {
-        scheduleTimer(interval);
-    }
+    updateReportingType(reportingType);
 
     if (enabled)
     {
@@ -119,7 +118,7 @@
 }
 
 uint64_t Report::getSensorCount(
-    std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
+    const std::vector<std::shared_ptr<interfaces::Metric>>& metrics)
 {
     uint64_t sensorCount = 0;
     for (auto& metric : metrics)
@@ -188,7 +187,7 @@
             {
                 if (true == newVal && ReportingType::periodic == reportingType)
                 {
-                    scheduleTimer(interval);
+                    scheduleTimerForPeriodicReport(interval);
                 }
                 if (newVal)
                 {
@@ -260,12 +259,6 @@
             ReportingType tmp = utils::toReportingType(newVal);
             if (tmp != reportingType)
             {
-                if (tmp == ReportingType::onChange)
-                {
-                    throw sdbusplus::exception::SdBusError(
-                        static_cast<int>(std::errc::invalid_argument),
-                        "Invalid reportingType");
-                }
                 if (tmp == ReportingType::periodic)
                 {
                     if (interval < ReportManager::minInterval)
@@ -274,20 +267,13 @@
                             static_cast<int>(std::errc::invalid_argument),
                             "Invalid interval");
                     }
-                    if (enabled == true)
-                    {
-                        scheduleTimer(interval);
-                    }
                 }
-                else
-                {
-                    timer.cancel();
-                }
-                reportingType = tmp;
+
+                updateReportingType(tmp);
                 setReadingBuffer(reportUpdates);
                 persistency = storeConfiguration();
+                oldVal = std::move(newVal);
             }
-            oldVal = std::move(newVal);
             return 1;
         },
         [this](const auto&) { return utils::enumToString(reportingType); });
@@ -377,7 +363,8 @@
     return dbusIface;
 }
 
-void Report::timerProc(boost::system::error_code ec, Report& self)
+void Report::timerProcForPeriodicReport(boost::system::error_code ec,
+                                        Report& self)
 {
     if (ec)
     {
@@ -385,14 +372,58 @@
     }
 
     self.updateReadings();
-    self.scheduleTimer(self.interval);
+    self.scheduleTimerForPeriodicReport(self.interval);
 }
 
-void Report::scheduleTimer(Milliseconds timerInterval)
+void Report::timerProcForOnChangeReport(boost::system::error_code ec,
+                                        Report& self)
 {
+    if (ec)
+    {
+        return;
+    }
+
+    const auto ensure =
+        utils::Ensure{[&self] { self.onChangeContext = std::nullopt; }};
+
+    self.onChangeContext.emplace(self);
+
+    const auto steadyTimestamp = self.clock->steadyTimestamp();
+
+    for (auto& metric : self.metrics)
+    {
+        metric->updateReadings(steadyTimestamp);
+    }
+
+    self.scheduleTimerForOnChangeReport();
+}
+
+void Report::scheduleTimerForPeriodicReport(Milliseconds timerInterval)
+{
+    if (!enabled)
+    {
+        return;
+    }
+
     timer.expires_after(timerInterval);
-    timer.async_wait(
-        [this](boost::system::error_code ec) { timerProc(ec, *this); });
+    timer.async_wait([this](boost::system::error_code ec) {
+        timerProcForPeriodicReport(ec, *this);
+    });
+}
+
+void Report::scheduleTimerForOnChangeReport()
+{
+    if (!enabled)
+    {
+        return;
+    }
+
+    constexpr Milliseconds timerInterval{100};
+
+    timer.expires_after(timerInterval);
+    timer.async_wait([this](boost::system::error_code ec) {
+        timerProcForOnChangeReport(ec, *this);
+    });
 }
 
 void Report::updateReadings()
@@ -495,3 +526,62 @@
 
     return result;
 }
+
+void Report::metricUpdated()
+{
+    if (onChangeContext)
+    {
+        onChangeContext->metricUpdated();
+        return;
+    }
+
+    updateReadings();
+}
+
+void Report::updateReportingType(ReportingType newReportingType)
+{
+    if (reportingType != newReportingType)
+    {
+        timer.cancel();
+        unregisterFromMetrics = nullptr;
+    }
+
+    reportingType = newReportingType;
+
+    switch (reportingType)
+    {
+        case ReportingType::periodic:
+        {
+            scheduleTimerForPeriodicReport(interval);
+            break;
+        }
+        case ReportingType::onChange:
+        {
+            unregisterFromMetrics = [this] {
+                for (auto& metric : metrics)
+                {
+                    metric->unregisterFromUpdates(*this);
+                }
+            };
+
+            bool isTimerRequired = false;
+
+            for (auto& metric : metrics)
+            {
+                metric->registerForUpdates(*this);
+                if (metric->isTimerRequired())
+                {
+                    isTimerRequired = true;
+                }
+            }
+
+            if (isTimerRequired)
+            {
+                scheduleTimerForOnChangeReport();
+            }
+            break;
+        }
+        default:
+            break;
+    }
+}
diff --git a/src/report.hpp b/src/report.hpp
index 3b8b451..213265c 100644
--- a/src/report.hpp
+++ b/src/report.hpp
@@ -3,6 +3,7 @@
 #include "interfaces/clock.hpp"
 #include "interfaces/json_storage.hpp"
 #include "interfaces/metric.hpp"
+#include "interfaces/metric_listener.hpp"
 #include "interfaces/report.hpp"
 #include "interfaces/report_factory.hpp"
 #include "interfaces/report_manager.hpp"
@@ -11,6 +12,7 @@
 #include "types/report_updates.hpp"
 #include "types/reporting_type.hpp"
 #include "utils/circular_vector.hpp"
+#include "utils/ensure.hpp"
 #include "utils/messanger.hpp"
 
 #include <boost/asio/io_context.hpp>
@@ -21,8 +23,32 @@
 #include <memory>
 #include <unordered_set>
 
-class Report : public interfaces::Report
+class Report : public interfaces::Report, public interfaces::MetricListener
 {
+    class OnChangeContext
+    {
+      public:
+        OnChangeContext(Report& report) : report(report)
+        {}
+
+        ~OnChangeContext()
+        {
+            if (updated)
+            {
+                report.updateReadings();
+            }
+        }
+
+        void metricUpdated()
+        {
+            updated = true;
+        }
+
+      private:
+        Report& report;
+        bool updated = false;
+    };
+
   public:
     Report(boost::asio::io_context& ioc,
            const std::shared_ptr<sdbusplus::asio::object_server>& objServer,
@@ -51,11 +77,17 @@
         return reportDir + id;
     }
 
+    void metricUpdated() override;
+
   private:
     std::unique_ptr<sdbusplus::asio::dbus_interface>
         makeReportInterface(const interfaces::ReportFactory& reportFactory);
-    static void timerProc(boost::system::error_code, Report& self);
-    void scheduleTimer(Milliseconds interval);
+    static void timerProcForPeriodicReport(boost::system::error_code,
+                                           Report& self);
+    static void timerProcForOnChangeReport(boost::system::error_code,
+                                           Report& self);
+    void scheduleTimerForPeriodicReport(Milliseconds interval);
+    void scheduleTimerForOnChangeReport();
     std::optional<uint64_t>
         deduceAppendLimit(const uint64_t appendLimitIn) const;
     uint64_t deduceBufferSize(const ReportUpdates reportUpdatesIn,
@@ -63,12 +95,13 @@
     void setReadingBuffer(const ReportUpdates newReportUpdates);
     void setReportUpdates(const ReportUpdates newReportUpdates);
     static uint64_t getSensorCount(
-        std::vector<std::shared_ptr<interfaces::Metric>>& metrics);
+        const std::vector<std::shared_ptr<interfaces::Metric>>& metrics);
     interfaces::JsonStorage::FilePath fileName() const;
     std::unordered_set<std::string>
         collectTriggerIds(boost::asio::io_context& ioc) const;
     bool storeConfiguration() const;
     void updateReadings();
+    void updateReportingType(ReportingType);
 
     std::string id;
     std::string name;
@@ -94,6 +127,8 @@
     bool enabled;
     std::unique_ptr<interfaces::Clock> clock;
     utils::Messanger messanger;
+    std::optional<OnChangeContext> onChangeContext;
+    utils::Ensure<std::function<void()>> unregisterFromMetrics;
 
   public:
     static constexpr const char* reportIfaceName =
diff --git a/src/report_factory.cpp b/src/report_factory.cpp
index 039846e..0d7f011 100644
--- a/src/report_factory.cpp
+++ b/src/report_factory.cpp
@@ -26,7 +26,7 @@
     std::vector<LabeledMetricParameters> labeledMetricParams,
     bool enabled) const
 {
-    std::vector<std::shared_ptr<interfaces::Metric>> metrics = utils::transform(
+    auto metrics = utils::transform(
         labeledMetricParams,
         [this](const LabeledMetricParameters& param)
             -> std::shared_ptr<interfaces::Metric> {
diff --git a/src/report_manager.cpp b/src/report_manager.cpp
index 10035a1..7600088 100644
--- a/src/report_manager.cpp
+++ b/src/report_manager.cpp
@@ -139,13 +139,6 @@
     const ReportUpdates reportUpdates, const uint64_t appendLimit,
     const std::vector<LabeledMetricParameters>& readingParams)
 {
-    if (reportingType == ReportingType::onChange)
-    {
-        throw sdbusplus::exception::SdBusError(
-            static_cast<int>(std::errc::invalid_argument),
-            "Invalid reportingType");
-    }
-
     if (reports.size() >= maxReports)
     {
         throw sdbusplus::exception::SdBusError(
diff --git a/src/sensor.cpp b/src/sensor.cpp
index 2551562..387d0d5 100644
--- a/src/sensor.cpp
+++ b/src/sensor.cpp
@@ -107,17 +107,7 @@
 {
     timestamp = Clock().steadyTimestamp();
 
-    if (value == newValue)
-    {
-        for (const auto& weakListener : listeners)
-        {
-            if (auto listener = weakListener.lock())
-            {
-                listener->sensorUpdated(*this, timestamp);
-            }
-        }
-    }
-    else
+    if (value != newValue)
     {
         value = newValue;
 
diff --git a/src/utils/ensure.hpp b/src/utils/ensure.hpp
new file mode 100644
index 0000000..cbe69c5
--- /dev/null
+++ b/src/utils/ensure.hpp
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <optional>
+#include <utility>
+
+namespace utils
+{
+
+template <class F>
+struct Ensure
+{
+    Ensure() = default;
+
+    template <class U>
+    Ensure(U&& functor) : functor(std::forward<U>(functor))
+    {}
+
+    Ensure(F functor) : functor(std::move(functor))
+    {}
+
+    Ensure(Ensure&& other) : functor(std::move(other.functor))
+    {
+        other.functor = std::nullopt;
+    }
+
+    Ensure(const Ensure&) = delete;
+
+    ~Ensure()
+    {
+        clear();
+    }
+
+    template <class U>
+    Ensure& operator=(U&& other)
+    {
+        clear();
+        functor = std::move(other);
+        return *this;
+    }
+
+    Ensure& operator=(Ensure&& other)
+    {
+        clear();
+        std::swap(functor, other.functor);
+        return *this;
+    }
+
+    Ensure& operator=(std::nullptr_t)
+    {
+        clear();
+        return *this;
+    }
+
+    Ensure& operator=(const Ensure&) = delete;
+
+  private:
+    void clear()
+    {
+        if (functor)
+        {
+            (*functor)();
+            functor = std::nullopt;
+        }
+    }
+
+    std::optional<F> functor;
+};
+
+} // namespace utils
diff --git a/tests/meson.build b/tests/meson.build
index 89c6a00..5c21d88 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -11,9 +11,10 @@
     executable(
         'telemetry-ut',
         [
-            '../src/details/collection_function.cpp',
             '../src/discrete_threshold.cpp',
             '../src/metric.cpp',
+            '../src/metrics/collection_data.cpp',
+            '../src/metrics/collection_function.cpp',
             '../src/numeric_threshold.cpp',
             '../src/on_change_threshold.cpp',
             '../src/persistent_json_storage.cpp',
@@ -36,6 +37,7 @@
             'src/test_conversion.cpp',
             'src/test_detached_timer.cpp',
             'src/test_discrete_threshold.cpp',
+            'src/test_ensure.cpp',
             'src/test_generate_id.cpp',
             'src/test_metric.cpp',
             'src/test_numeric_threshold.cpp',
diff --git a/tests/src/mocks/metric_listener_mock.hpp b/tests/src/mocks/metric_listener_mock.hpp
new file mode 100644
index 0000000..2e3daf9
--- /dev/null
+++ b/tests/src/mocks/metric_listener_mock.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "interfaces/metric_listener.hpp"
+
+#include <gmock/gmock.h>
+
+class MetricListenerMock : public interfaces::MetricListener
+{
+  public:
+    MOCK_METHOD(void, metricUpdated, (), (override));
+};
diff --git a/tests/src/mocks/metric_mock.hpp b/tests/src/mocks/metric_mock.hpp
index 268b681..a223734 100644
--- a/tests/src/mocks/metric_mock.hpp
+++ b/tests/src/mocks/metric_mock.hpp
@@ -13,6 +13,9 @@
 
         ON_CALL(*this, getReadings())
             .WillByDefault(Return(std::vector<MetricValue>()));
+        ON_CALL(*this, sensorCount).WillByDefault(InvokeWithoutArgs([this] {
+            return getReadings().size();
+        }));
     }
 
     MOCK_METHOD(void, initialize, (), (override));
@@ -20,9 +23,11 @@
     MOCK_METHOD(std::vector<MetricValue>, getReadings, (), (const, override));
     MOCK_METHOD(LabeledMetricParameters, dumpConfiguration, (),
                 (const, override));
-
-    uint64_t sensorCount() const override
-    {
-        return getReadings().size();
-    }
+    MOCK_METHOD(uint64_t, sensorCount, (), (const, override));
+    MOCK_METHOD(void, registerForUpdates, (interfaces::MetricListener&),
+                (override));
+    MOCK_METHOD(void, unregisterFromUpdates, (interfaces::MetricListener&),
+                (override));
+    MOCK_METHOD(void, updateReadings, (Milliseconds), (override));
+    MOCK_METHOD(bool, isTimerRequired, (), (const, override));
 };
diff --git a/tests/src/mocks/sensor_listener_mock.hpp b/tests/src/mocks/sensor_listener_mock.hpp
index a5c0331..d23e349 100644
--- a/tests/src/mocks/sensor_listener_mock.hpp
+++ b/tests/src/mocks/sensor_listener_mock.hpp
@@ -7,21 +7,6 @@
 class SensorListenerMock : public interfaces::SensorListener
 {
   public:
-    void delegateIgnoringArgs()
-    {
-        using namespace testing;
-
-        ON_CALL(*this, sensorUpdated(_, _))
-            .WillByDefault(InvokeWithoutArgs([this] { sensorUpdated(); }));
-
-        ON_CALL(*this, sensorUpdated(_, _, _))
-            .WillByDefault(InvokeWithoutArgs([this] { sensorUpdated(); }));
-    }
-
-    MOCK_METHOD(void, sensorUpdated, (interfaces::Sensor&, Milliseconds),
-                (override));
     MOCK_METHOD(void, sensorUpdated,
                 (interfaces::Sensor&, Milliseconds, double), (override));
-
-    MOCK_METHOD(void, sensorUpdated, (), ());
 };
diff --git a/tests/src/params/metric_params.hpp b/tests/src/params/metric_params.hpp
index 90db484..cc5519d 100644
--- a/tests/src/params/metric_params.hpp
+++ b/tests/src/params/metric_params.hpp
@@ -79,6 +79,17 @@
         return expectedReadingProperty;
     }
 
+    bool expectedIsTimerRequired() const
+    {
+        return expectedIsTimerRequiredProperty;
+    }
+
+    MetricParams& expectedIsTimerRequired(bool value)
+    {
+        expectedIsTimerRequiredProperty = value;
+        return *this;
+    }
+
   private:
     OperationType operationTypeProperty = {};
     std::string idProperty = "MetricId";
@@ -87,6 +98,7 @@
         CollectionDuration(Milliseconds(0u));
     std::vector<std::pair<Milliseconds, double>> readingsProperty = {};
     std::pair<Milliseconds, double> expectedReadingProperty = {};
+    bool expectedIsTimerRequiredProperty = true;
 };
 
 inline std::ostream& operator<<(std::ostream& os, const MetricParams& mp)
@@ -103,6 +115,8 @@
     }
 
     auto [timestamp, reading] = mp.expectedReading();
-    os << " }, expected: " << reading << "(" << timestamp.count() << "ms) }";
+    os << " }, expectedReading: " << reading << "(" << timestamp.count()
+       << "ms), expectedIsTimerRequired: " << std::boolalpha
+       << mp.expectedIsTimerRequired() << " }";
     return os;
 }
diff --git a/tests/src/test_ensure.cpp b/tests/src/test_ensure.cpp
new file mode 100644
index 0000000..b604538
--- /dev/null
+++ b/tests/src/test_ensure.cpp
@@ -0,0 +1,52 @@
+#include "utils/ensure.hpp"
+
+#include <gmock/gmock.h>
+
+using namespace testing;
+
+class TestEnsure : public Test
+{
+  public:
+    utils::Ensure<std::function<void()>> makeEnsure()
+    {
+        return [this] { ++executed; };
+    }
+
+    utils::Ensure<std::function<void()>> sut;
+    size_t executed = 0u;
+};
+
+TEST_F(TestEnsure, executesCallbackOnceWhenDestroyed)
+{
+    sut = makeEnsure();
+    sut = nullptr;
+
+    EXPECT_THAT(executed, Eq(1u));
+}
+
+TEST_F(TestEnsure, executesCallbackOnceWhenMoved)
+{
+    sut = makeEnsure();
+    auto copy = std::move(sut);
+    copy = nullptr;
+
+    EXPECT_THAT(executed, Eq(1u));
+}
+
+TEST_F(TestEnsure, executesCallbackTwiceWhenReplaced)
+{
+    sut = makeEnsure();
+    sut = makeEnsure();
+    sut = nullptr;
+
+    EXPECT_THAT(executed, Eq(2u));
+}
+
+TEST_F(TestEnsure, executesCallbackTwiceWhenNewCallbackAssigned)
+{
+    sut = makeEnsure();
+    sut = [this] { executed += 10; };
+    sut = nullptr;
+
+    EXPECT_THAT(executed, Eq(11u));
+}
diff --git a/tests/src/test_metric.cpp b/tests/src/test_metric.cpp
index 31eabc6..f30b4f3 100644
--- a/tests/src/test_metric.cpp
+++ b/tests/src/test_metric.cpp
@@ -1,6 +1,7 @@
 #include "fakes/clock_fake.hpp"
 #include "helpers.hpp"
 #include "metric.hpp"
+#include "mocks/metric_listener_mock.hpp"
 #include "mocks/sensor_mock.hpp"
 #include "params/metric_params.hpp"
 #include "utils/conv_container.hpp"
@@ -55,6 +56,7 @@
     std::vector<std::shared_ptr<SensorMock>> sensorMocks = makeSensorMocks(1u);
     std::unique_ptr<ClockFake> clockFakePtr = std::make_unique<ClockFake>();
     ClockFake& clockFake = *clockFakePtr;
+    NiceMock<MetricListenerMock> listenerMock;
     std::shared_ptr<Metric> sut;
 };
 
@@ -90,6 +92,34 @@
                 ElementsAre(MetricValue({"id", "metadata", 0., 0u})));
 }
 
+TEST_F(TestMetric,
+       notifiesRegisteredListenersOnManualUpdateWhenMetricValueChanges)
+{
+    sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup));
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
+    sut->registerForUpdates(listenerMock);
+
+    EXPECT_CALL(listenerMock, metricUpdated()).Times(2);
+
+    sut->updateReadings(Milliseconds{50u});
+    sut->updateReadings(Milliseconds{100u});
+}
+
+TEST_F(TestMetric,
+       doesntNotifyRegisteredListenersOnManualUpdateWhenMetricValueDoesntChange)
+{
+    sut = makeSut(params.collectionTimeScope(CollectionTimeScope::startup)
+                      .operationType(OperationType::max));
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
+    sut->registerForUpdates(listenerMock);
+
+    EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
+
+    sut->updateReadings(Milliseconds{50u});
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{180}, 11.);
+    sut->updateReadings(Milliseconds{100u});
+}
+
 class TestMetricAfterInitialization : public TestMetric
 {
   public:
@@ -122,8 +152,6 @@
        throwsWhenUpdateIsPerformedOnUnknownSensor)
 {
     auto sensor = std::make_shared<StrictMock<SensorMock>>();
-    EXPECT_THROW(sut->sensorUpdated(*sensor, Milliseconds{10}),
-                 std::out_of_range);
     EXPECT_THROW(sut->sensorUpdated(*sensor, Milliseconds{10}, 20.0),
                  std::out_of_range);
 }
@@ -150,6 +178,33 @@
     EXPECT_THAT(conf, Eq(expected));
 }
 
+TEST_F(TestMetricAfterInitialization, notifiesRegisteredListeners)
+{
+    EXPECT_CALL(listenerMock, metricUpdated());
+
+    sut->registerForUpdates(listenerMock);
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
+}
+
+TEST_F(TestMetricAfterInitialization,
+       doesntNotifyRegisteredListenersWhenValueDoesntChange)
+{
+    EXPECT_CALL(listenerMock, metricUpdated());
+
+    sut->registerForUpdates(listenerMock);
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{70}, 31.2);
+}
+
+TEST_F(TestMetricAfterInitialization, doesntNotifyAfterUnRegisterListener)
+{
+    EXPECT_CALL(listenerMock, metricUpdated()).Times(0);
+
+    sut->registerForUpdates(listenerMock);
+    sut->unregisterFromUpdates(listenerMock);
+    sut->sensorUpdated(*sensorMocks.front(), Milliseconds{18}, 31.2);
+}
+
 class TestMetricCalculationFunctions :
     public TestMetric,
     public WithParamInterface<MetricParams>
@@ -182,8 +237,9 @@
 
 MetricParams defaultPointParams()
 {
-    return defaultCollectionFunctionParams().collectionTimeScope(
-        CollectionTimeScope::point);
+    return defaultCollectionFunctionParams()
+        .collectionTimeScope(CollectionTimeScope::point)
+        .expectedIsTimerRequired(false);
 }
 
 INSTANTIATE_TEST_SUITE_P(
@@ -210,7 +266,8 @@
                .expectedReading(systemTimestamp + 16ms, 7.0),
            defaultMinParams()
                .collectionTimeScope(CollectionTimeScope::startup)
-               .expectedReading(systemTimestamp + 16ms, 3.0)));
+               .expectedReading(systemTimestamp + 16ms, 3.0)
+               .expectedIsTimerRequired(false)));
 
 MetricParams defaultMaxParams()
 {
@@ -233,7 +290,8 @@
                .expectedReading(systemTimestamp + 16ms, 7.0),
            defaultMaxParams()
                .collectionTimeScope(CollectionTimeScope::startup)
-               .expectedReading(systemTimestamp + 16ms, 14.0)));
+               .expectedReading(systemTimestamp + 16ms, 14.0)
+               .expectedIsTimerRequired(false)));
 
 MetricParams defaultSumParams()
 {
@@ -324,3 +382,9 @@
                 ElementsAre(MetricValue{"id", "metadata", expectedReading,
                                         expectedTimestamp.count()}));
 }
+
+TEST_P(TestMetricCalculationFunctions, returnsIsTimerRequired)
+{
+    EXPECT_THAT(sut->isTimerRequired(),
+                Eq(GetParam().expectedIsTimerRequired()));
+}
diff --git a/tests/src/test_report.cpp b/tests/src/test_report.cpp
index 9afa421..7646230 100644
--- a/tests/src/test_report.cpp
+++ b/tests/src/test_report.cpp
@@ -27,11 +27,24 @@
 
 constexpr Milliseconds systemTimestamp = 55ms;
 
+namespace
+{
+
+ReportParams defaultParams()
+{
+    return ReportParams();
+}
+
+ReportParams defaultOnChangeParams()
+{
+    return defaultParams().reportingType(ReportingType::onChange);
+}
+
+} // namespace
+
 class TestReport : public Test
 {
   public:
-    ReportParams defaultParams;
-
     std::unique_ptr<ReportManagerMock> reportManagerMock =
         std::make_unique<NiceMock<ReportManagerMock>>();
     std::unique_ptr<ReportFactoryMock> reportFactoryMock =
@@ -92,7 +105,7 @@
 
     void SetUp() override
     {
-        sut = makeReport(ReportParams());
+        sut = makeReport(defaultParams());
     }
 
     static interfaces::JsonStorage::FilePath to_file_path(std::string id)
@@ -157,39 +170,39 @@
 
 TEST_F(TestReport, returnsId)
 {
-    EXPECT_THAT(sut->getId(), Eq(defaultParams.reportId()));
+    EXPECT_THAT(sut->getId(), Eq(defaultParams().reportId()));
 }
 
 TEST_F(TestReport, verifyIfPropertiesHaveValidValue)
 {
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"),
-                Eq(defaultParams.enabled()));
+                Eq(defaultParams().enabled()));
     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
-                Eq(defaultParams.interval().count()));
+                Eq(defaultParams().interval().count()));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Persistency"), Eq(true));
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
-        Eq(utils::transform(defaultParams.reportActions(), [](const auto v) {
+        Eq(utils::transform(defaultParams().reportActions(), [](const auto v) {
             return utils::enumToString(v);
         })));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
-                Eq(utils::contains(defaultParams.reportActions(),
+                Eq(utils::contains(defaultParams().reportActions(),
                                    ReportAction::emitsReadingsUpdate)));
     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "AppendLimit"),
-                Eq(defaultParams.appendLimit()));
+                Eq(defaultParams().appendLimit()));
     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportingType"),
-                Eq(utils::enumToString(defaultParams.reportingType())));
+                Eq(utils::enumToString(defaultParams().reportingType())));
     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "ReportUpdates"),
-                Eq(utils::enumToString(defaultParams.reportUpdates())));
+                Eq(utils::enumToString(defaultParams().reportUpdates())));
     EXPECT_THAT(
         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
-        Eq(utils::contains(defaultParams.reportActions(),
+        Eq(utils::contains(defaultParams().reportActions(),
                            ReportAction::logToMetricReportsCollection)));
     EXPECT_THAT(getProperty<ReadingParameters>(
                     sut->getPath(), "ReadingParametersFutureVersion"),
-                Eq(toReadingParameters(defaultParams.metricParameters())));
+                Eq(toReadingParameters(defaultParams().metricParameters())));
     EXPECT_THAT(getProperty<std::string>(sut->getPath(), "Name"),
-                Eq(defaultParams.reportName()));
+                Eq(defaultParams().reportName()));
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerIds"),
         Eq(std::vector<std::string>()));
@@ -228,7 +241,7 @@
 TEST_F(TestReport, setReportingTypeWithValidNewType)
 {
     std::string newType = "Periodic";
-    std::string currType = utils::enumToString(defaultParams.reportingType());
+    std::string currType = utils::enumToString(defaultParams().reportingType());
 
     EXPECT_THAT(newType, Ne(currType));
     EXPECT_THAT(setProperty(sut->getPath(), "ReportingType", newType).value(),
@@ -240,7 +253,7 @@
 TEST_F(TestReport, setReportingTypeWithInvalidType)
 {
     std::string newType = "Periodic_ABC";
-    std::string prevType = utils::enumToString(defaultParams.reportingType());
+    std::string prevType = utils::enumToString(defaultParams().reportingType());
 
     EXPECT_THAT(setProperty(sut->getPath(), "ReportingType", newType).value(),
                 Eq(boost::system::errc::invalid_argument));
@@ -252,7 +265,7 @@
 {
     std::vector<std::string> newActions = {"EmitsReadingsUpdate"};
     std::vector<std::string> currActions =
-        utils::transform(defaultParams.reportActions(),
+        utils::transform(defaultParams().reportActions(),
                          [](const auto v) { return utils::enumToString(v); });
 
     EXPECT_THAT(newActions, Ne(currActions));
@@ -272,7 +285,7 @@
     std::vector<std::string> expectedActions = {"EmitsReadingsUpdate",
                                                 "LogToMetricReportsCollection"};
     std::vector<std::string> currActions =
-        utils::transform(defaultParams.reportActions(),
+        utils::transform(defaultParams().reportActions(),
                          [](const auto v) { return utils::enumToString(v); });
 
     EXPECT_THAT(newActions, Ne(currActions));
@@ -289,7 +302,7 @@
     std::vector<std::string> newActions = {};
     std::vector<std::string> expectedActions = {"LogToMetricReportsCollection"};
     std::vector<std::string> currActions =
-        utils::transform(defaultParams.reportActions(),
+        utils::transform(defaultParams().reportActions(),
                          [](const auto v) { return utils::enumToString(v); });
 
     EXPECT_THAT(newActions, Ne(currActions));
@@ -309,7 +322,7 @@
         Eq(boost::system::errc::invalid_argument));
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "ReportActions"),
-        Eq(utils::transform(defaultParams.reportActions(), [](const auto v) {
+        Eq(utils::transform(defaultParams().reportActions(), [](const auto v) {
             return utils::enumToString(v);
         })));
 }
@@ -344,7 +357,7 @@
 
 TEST_F(TestReport, setEnabledWithNewValue)
 {
-    bool newValue = !defaultParams.enabled();
+    bool newValue = !defaultParams().enabled();
     EXPECT_THAT(setProperty(sut->getPath(), "Enabled", newValue).value(),
                 Eq(boost::system::errc::success));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "Enabled"), Eq(newValue));
@@ -352,7 +365,7 @@
 
 TEST_F(TestReport, setIntervalWithValidValue)
 {
-    uint64_t newValue = defaultParams.interval().count() + 1;
+    uint64_t newValue = defaultParams().interval().count() + 1;
     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
                 Eq(boost::system::errc::success));
     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
@@ -363,11 +376,11 @@
     TestReport,
     settingIntervalWithInvalidValueDoesNotChangePropertyAndReturnsInvalidArgument)
 {
-    uint64_t newValue = defaultParams.interval().count() - 1;
+    uint64_t newValue = defaultParams().interval().count() - 1;
     EXPECT_THAT(setProperty(sut->getPath(), "Interval", newValue).value(),
                 Eq(boost::system::errc::invalid_argument));
     EXPECT_THAT(getProperty<uint64_t>(sut->getPath(), "Interval"),
-                Eq(defaultParams.interval().count()));
+                Eq(defaultParams().interval().count()));
 }
 
 TEST_F(TestReport, settingEmitsReadingsUpdateHaveNoEffect)
@@ -376,7 +389,7 @@
         setProperty(sut->getPath(), "EmitsReadingsUpdate", true).value(),
         Eq(boost::system::errc::read_only_file_system));
     EXPECT_THAT(getProperty<bool>(sut->getPath(), "EmitsReadingsUpdate"),
-                Eq(utils::contains(defaultParams.reportActions(),
+                Eq(utils::contains(defaultParams().reportActions(),
                                    ReportAction::emitsReadingsUpdate)));
 }
 
@@ -388,7 +401,7 @@
         Eq(boost::system::errc::read_only_file_system));
     EXPECT_THAT(
         getProperty<bool>(sut->getPath(), "LogToMetricReportsCollection"),
-        Eq(utils::contains(defaultParams.reportActions(),
+        Eq(utils::contains(defaultParams().reportActions(),
                            ReportAction::logToMetricReportsCollection)));
 }
 
@@ -428,15 +441,15 @@
     utils::Messanger messanger(DbusEnvironment::getIoc());
 
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
         messages::Presence::Exist, "trigger2", {"someOtherReport"}});
     messanger.send(messages::TriggerPresenceChangedInd{
         messages::Presence::Exist,
         "trigger3",
-        {"someOtherReport", defaultParams.reportId()}});
+        {"someOtherReport", defaultParams().reportId()}});
 
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerIds"),
@@ -448,18 +461,18 @@
     utils::Messanger messanger(DbusEnvironment::getIoc());
 
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger2", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger2", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger3", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});
 
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Removed, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Removed, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
         messages::Presence::Removed, "trigger2", {}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Removed, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Removed, "trigger1", {defaultParams().reportId()}});
 
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerIds"),
@@ -471,18 +484,18 @@
     utils::Messanger messanger(DbusEnvironment::getIoc());
 
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger2", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger2", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger3", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});
 
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger1", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger1", {defaultParams().reportId()}});
     messanger.send(messages::TriggerPresenceChangedInd{
         messages::Presence::Exist, "trigger2", {}});
     messanger.send(messages::TriggerPresenceChangedInd{
-        messages::Presence::Exist, "trigger3", {defaultParams.reportId()}});
+        messages::Presence::Exist, "trigger3", {defaultParams().reportId()}});
 
     EXPECT_THAT(
         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerIds"),
@@ -502,49 +515,49 @@
 
 INSTANTIATE_TEST_SUITE_P(
     _, TestReportStore,
-    Values(std::make_pair("Enabled"s, nlohmann::json(ReportParams().enabled())),
-           std::make_pair("Version"s, nlohmann::json(6)),
-           std::make_pair("Id"s, nlohmann::json(ReportParams().reportId())),
-           std::make_pair("Name"s, nlohmann::json(ReportParams().reportName())),
-           std::make_pair("ReportingType",
-                          nlohmann::json(ReportParams().reportingType())),
-           std::make_pair("ReportActions", nlohmann::json(utils::transform(
-                                               ReportParams().reportActions(),
-                                               [](const auto v) {
-                                                   return utils::toUnderlying(
-                                                       v);
-                                               }))),
-           std::make_pair("Interval",
-                          nlohmann::json(ReportParams().interval().count())),
-           std::make_pair("AppendLimit",
-                          nlohmann::json(ReportParams().appendLimit())),
-           std::make_pair(
-               "ReadingParameters",
-               nlohmann::json(
-                   {{{tstring::SensorPath::str(),
-                      {{{tstring::Service::str(), "Service"},
-                        {tstring::Path::str(),
-                         "/xyz/openbmc_project/sensors/power/p1"},
-                        {tstring::Metadata::str(), "metadata1"}}}},
-                     {tstring::OperationType::str(), OperationType::avg},
-                     {tstring::Id::str(), "MetricId1"},
-                     {tstring::CollectionTimeScope::str(),
-                      CollectionTimeScope::point},
-                     {tstring::CollectionDuration::str(), 0}},
-                    {{tstring::SensorPath::str(),
-                      {{{tstring::Service::str(), "Service"},
-                        {tstring::Path::str(),
-                         "/xyz/openbmc_project/sensors/power/p2"},
-                        {tstring::Metadata::str(), "metadata2"}}}},
-                     {tstring::OperationType::str(), OperationType::avg},
-                     {tstring::Id::str(), "MetricId2"},
-                     {tstring::CollectionTimeScope::str(),
-                      CollectionTimeScope::point},
-                     {tstring::CollectionDuration::str(), 0}}}))));
+    Values(
+        std::make_pair("Enabled"s, nlohmann::json(defaultParams().enabled())),
+        std::make_pair("Version"s, nlohmann::json(6)),
+        std::make_pair("Id"s, nlohmann::json(defaultParams().reportId())),
+        std::make_pair("Name"s, nlohmann::json(defaultParams().reportName())),
+        std::make_pair("ReportingType",
+                       nlohmann::json(defaultParams().reportingType())),
+        std::make_pair("ReportActions", nlohmann::json(utils::transform(
+                                            defaultParams().reportActions(),
+                                            [](const auto v) {
+                                                return utils::toUnderlying(v);
+                                            }))),
+        std::make_pair("Interval",
+                       nlohmann::json(defaultParams().interval().count())),
+        std::make_pair("AppendLimit",
+                       nlohmann::json(ReportParams().appendLimit())),
+        std::make_pair(
+            "ReadingParameters",
+            nlohmann::json(
+                {{{tstring::SensorPath::str(),
+                   {{{tstring::Service::str(), "Service"},
+                     {tstring::Path::str(),
+                      "/xyz/openbmc_project/sensors/power/p1"},
+                     {tstring::Metadata::str(), "metadata1"}}}},
+                  {tstring::OperationType::str(), OperationType::avg},
+                  {tstring::Id::str(), "MetricId1"},
+                  {tstring::CollectionTimeScope::str(),
+                   CollectionTimeScope::point},
+                  {tstring::CollectionDuration::str(), 0}},
+                 {{tstring::SensorPath::str(),
+                   {{{tstring::Service::str(), "Service"},
+                     {tstring::Path::str(),
+                      "/xyz/openbmc_project/sensors/power/p2"},
+                     {tstring::Metadata::str(), "metadata2"}}}},
+                  {tstring::OperationType::str(), OperationType::avg},
+                  {tstring::Id::str(), "MetricId2"},
+                  {tstring::CollectionTimeScope::str(),
+                   CollectionTimeScope::point},
+                  {tstring::CollectionDuration::str(), 0}}}))));
 
 TEST_P(TestReportStore, settingPersistencyToTrueStoresReport)
 {
-    sut = makeReport(ReportParams());
+    sut = makeReport(defaultParams());
 
     {
         InSequence seq;
@@ -565,10 +578,10 @@
 
 TEST_P(TestReportStore, reportIsSavedToStorageAfterCreated)
 {
-    EXPECT_CALL(storageMock, store(to_file_path(ReportParams().reportId()), _))
+    EXPECT_CALL(storageMock, store(to_file_path(defaultParams().reportId()), _))
         .WillOnce(SaveArg<1>(&storedConfiguration));
 
-    sut = makeReport(ReportParams());
+    sut = makeReport(defaultParams());
 
     const auto& [key, value] = GetParam();
 
@@ -586,9 +599,9 @@
 
 INSTANTIATE_TEST_SUITE_P(
     ValidNames, TestReportValidNames,
-    Values(ReportParams().reportName("Valid_1"),
-           ReportParams().reportName("Valid_1/Valid_2"),
-           ReportParams().reportName("Valid_1/Valid_2/Valid_3")));
+    Values(defaultParams().reportName("Valid_1"),
+           defaultParams().reportName("Valid_1/Valid_2"),
+           defaultParams().reportName("Valid_1/Valid_2/Valid_3")));
 
 TEST_P(TestReportValidNames, reportCtorDoesNotThrowOnValidName)
 {
@@ -605,11 +618,11 @@
 };
 
 INSTANTIATE_TEST_SUITE_P(InvalidNames, TestReportInvalidIds,
-                         Values(ReportParams().reportId("/"),
-                                ReportParams().reportId("/Invalid"),
-                                ReportParams().reportId("Invalid/"),
-                                ReportParams().reportId("Invalid/Invalid/"),
-                                ReportParams().reportId("Invalid?")));
+                         Values(defaultParams().reportId("/"),
+                                defaultParams().reportId("/Invalid"),
+                                defaultParams().reportId("Invalid/"),
+                                defaultParams().reportId("Invalid/Invalid/"),
+                                defaultParams().reportId("Invalid?")));
 
 TEST_P(TestReportInvalidIds, failsToCreateReportWithInvalidName)
 {
@@ -631,9 +644,9 @@
 
 INSTANTIATE_TEST_SUITE_P(
     _, TestReportAllReportTypes,
-    Values(ReportParams().reportingType(ReportingType::onRequest),
-           ReportParams().reportingType(ReportingType::onChange),
-           ReportParams().reportingType(ReportingType::periodic)));
+    Values(defaultParams().reportingType(ReportingType::onRequest),
+           defaultParams().reportingType(ReportingType::onChange),
+           defaultParams().reportingType(ReportingType::periodic)));
 
 TEST_P(TestReportAllReportTypes, returnPropertValueOfReportType)
 {
@@ -681,7 +694,7 @@
     void SetUp() override
     {
         sut =
-            makeReport(ReportParams().reportingType(ReportingType::onRequest));
+            makeReport(defaultParams().reportingType(ReportingType::onRequest));
     }
 };
 
@@ -721,8 +734,8 @@
 
 INSTANTIATE_TEST_SUITE_P(
     _, TestReportNonOnRequestType,
-    Values(ReportParams().reportingType(ReportingType::periodic),
-           ReportParams().reportingType(ReportingType::onChange)));
+    Values(defaultParams().reportingType(ReportingType::periodic),
+           defaultParams().reportingType(ReportingType::onChange)));
 
 TEST_P(TestReportNonOnRequestType, readingsAreNotUpdateOnUpdateCall)
 {
@@ -745,8 +758,8 @@
 
 INSTANTIATE_TEST_SUITE_P(
     _, TestReportNonPeriodicReport,
-    Values(ReportParams().reportingType(ReportingType::onRequest),
-           ReportParams().reportingType(ReportingType::onChange)));
+    Values(defaultParams().reportingType(ReportingType::onRequest),
+           defaultParams().reportingType(ReportingType::onChange)));
 
 TEST_P(TestReportNonPeriodicReport, readingsAreNotUpdatedAfterIntervalExpires)
 {
@@ -760,7 +773,8 @@
 {
     void SetUp() override
     {
-        sut = makeReport(ReportParams().reportingType(ReportingType::periodic));
+        sut =
+            makeReport(defaultParams().reportingType(ReportingType::periodic));
     }
 };
 
@@ -810,7 +824,7 @@
     _, TestReportWithReportUpdatesAndLimit,
     Values(
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                 .appendLimit(5),
             std::vector<ReadingData>{{std::make_tuple("aa"s, "bb"s, 42.0, 74u),
@@ -820,7 +834,7 @@
                                       std::make_tuple("a"s, "b"s, 17.1, 114u)}},
             true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                 .appendLimit(4),
             std::vector<ReadingData>{
@@ -830,12 +844,12 @@
                  std::make_tuple("aa"s, "bb"s, 42.0, 74u)}},
             true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendWrapsWhenFull)
                 .appendLimit(0),
             std::vector<ReadingData>{}, true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendStopsWhenFull)
                 .appendLimit(10),
             std::vector<ReadingData>{
@@ -849,7 +863,7 @@
                  std::make_tuple("aa"s, "bb"s, 42.0, 74u)}},
             true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendStopsWhenFull)
                 .appendLimit(5),
             std::vector<ReadingData>{{std::make_tuple("a"s, "b"s, 17.1, 114u),
@@ -859,7 +873,7 @@
                                       std::make_tuple("a"s, "b"s, 17.1, 114u)}},
             false},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendStopsWhenFull)
                 .appendLimit(4),
             std::vector<ReadingData>{
@@ -869,12 +883,12 @@
                  std::make_tuple("aa"s, "bb"s, 42.0, 74u)}},
             false},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::appendStopsWhenFull)
                 .appendLimit(0),
             std::vector<ReadingData>{}, false},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::overwrite)
                 .appendLimit(500),
             std::vector<ReadingData>{
@@ -882,7 +896,7 @@
                  std::make_tuple("aa"s, "bb"s, 42.0, 74u)}},
             true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::overwrite)
                 .appendLimit(1),
             std::vector<ReadingData>{
@@ -890,7 +904,7 @@
                  std::make_tuple("aa"s, "bb"s, 42.0, 74u)}},
             true},
         ReportUpdatesReportParams{
-            ReportParams()
+            defaultParams()
                 .reportUpdates(ReportUpdates::overwrite)
                 .appendLimit(0),
             std::vector<ReadingData>{
@@ -918,7 +932,9 @@
 {
   public:
     void SetUp() override
-    {}
+    {
+        initMetricMocks(defaultParams().metricParameters());
+    }
 
     void monitorProc(sdbusplus::message::message& msg)
     {
@@ -955,25 +971,60 @@
 };
 
 TEST_F(TestReportInitialization,
-       metricsAreInitializedWhenEnabledReportConstructed)
+       registersForMetricUpdatesWhenOnChangeReportCreated)
 {
-    initMetricMocks(defaultParams.metricParameters());
+    std::vector<const interfaces::MetricListener*> args;
     for (auto& metric : metricMocks)
     {
-        EXPECT_CALL(*metric, initialize()).Times(1);
+        EXPECT_CALL(*metric, registerForUpdates(_))
+            .WillOnce(Invoke([&args](const interfaces::MetricListener& report) {
+                args.emplace_back(&report);
+            }));
+        ;
     }
-    sut = makeReport(defaultParams.enabled(true));
+
+    sut = makeReport(defaultParams().reportingType(ReportingType::onChange));
+
+    EXPECT_THAT(args, SizeIs(metricMocks.size()));
+    for (const auto* reportPtr : args)
+    {
+        EXPECT_THAT(reportPtr, Eq(sut.get()));
+    }
+}
+
+TEST_F(TestReportInitialization,
+       deregistersForMetricUpdatesWhenOnChangeReportDestroyed)
+{
+    sut = makeReport(defaultParams().reportingType(ReportingType::onChange));
+
+    for (auto& metric : metricMocks)
+    {
+        EXPECT_CALL(*metric,
+                    unregisterFromUpdates(Ref(
+                        static_cast<interfaces::MetricListener&>(*sut.get()))));
+    }
+
+    sut = nullptr;
+}
+
+TEST_F(TestReportInitialization,
+       metricsAreInitializedWhenEnabledReportConstructed)
+{
+    for (auto& metric : metricMocks)
+    {
+        EXPECT_CALL(*metric, initialize());
+    }
+    sut = makeReport(defaultParams().enabled(true));
 }
 
 TEST_F(TestReportInitialization,
        metricsAreNotInitializedWhenDisabledReportConstructed)
 {
-    initMetricMocks(defaultParams.metricParameters());
     for (auto& metric : metricMocks)
     {
         EXPECT_CALL(*metric, initialize()).Times(0);
     }
-    sut = makeReport(defaultParams.enabled(false));
+    sut = makeReport(defaultParams().enabled(false));
 }
 
 TEST_F(TestReportInitialization,
@@ -985,14 +1036,15 @@
 
     const auto elapsed = DbusEnvironment::measureTime([this] {
         sut =
-            makeReport(defaultParams.reportingType(ReportingType::periodic)
+            makeReport(defaultParams()
+                           .reportingType(ReportingType::periodic)
                            .reportActions({ReportAction::emitsReadingsUpdate}));
         makeMonitor();
         EXPECT_TRUE(DbusEnvironment::waitForFuture("readingsUpdated"));
     });
 
-    EXPECT_THAT(elapsed, AllOf(Ge(defaultParams.interval()),
-                               Lt(defaultParams.interval() * 2)));
+    EXPECT_THAT(elapsed, AllOf(Ge(defaultParams().interval()),
+                               Lt(defaultParams().interval() * 2)));
 }
 
 TEST_F(TestReportInitialization,
@@ -1000,16 +1052,17 @@
 {
     EXPECT_CALL(readingsUpdated, Call()).Times(0);
 
-    sut = makeReport(
-        defaultParams.reportingType(ReportingType::periodic).reportActions({}));
+    sut = makeReport(defaultParams()
+                         .reportingType(ReportingType::periodic)
+                         .reportActions({}));
     makeMonitor();
-    DbusEnvironment::sleepFor(defaultParams.interval() * 2);
+    DbusEnvironment::sleepFor(defaultParams().interval() * 2);
 }
 
 TEST_F(TestReportInitialization, appendLimitDeducedProperly)
 {
     sut = makeReport(
-        ReportParams().appendLimit(std::numeric_limits<uint64_t>::max()));
+        defaultParams().appendLimit(std::numeric_limits<uint64_t>::max()));
     auto appendLimit = getProperty<uint64_t>(sut->getPath(), "AppendLimit");
     EXPECT_EQ(appendLimit, 2ull);
 }
@@ -1044,3 +1097,41 @@
         getProperty<std::vector<std::string>>(sut->getPath(), "TriggerIds"),
         UnorderedElementsAre("trigger1", "trigger2"));
 }
+
+class TestReportInitializationOnChangeReport : public TestReportInitialization
+{
+  public:
+    void SetUp() override
+    {
+        initMetricMocks(params.metricParameters());
+    }
+
+    ReportParams params = defaultOnChangeParams();
+};
+
+TEST_F(TestReportInitializationOnChangeReport,
+       doesntUpdateReadingsWhenNotRequired)
+{
+    EXPECT_CALL(*metricMocks[0], updateReadings(_)).Times(0);
+
+    ON_CALL(*metricMocks[0], isTimerRequired()).WillByDefault(Return(false));
+
+    sut = makeReport(params);
+
+    DbusEnvironment::sleepFor(500ms);
+}
+
+TEST_F(TestReportInitializationOnChangeReport, updatesReadingsWhenRequired)
+{
+    EXPECT_CALL(*metricMocks[0], updateReadings(_))
+        .WillOnce(Return())
+        .WillOnce(
+            InvokeWithoutArgs(DbusEnvironment::setPromise("readingsUpdated")))
+        .WillRepeatedly(Return());
+
+    ON_CALL(*metricMocks[0], isTimerRequired()).WillByDefault(Return(true));
+
+    sut = makeReport(params);
+
+    DbusEnvironment::waitForFuture("readingsUpdated");
+}
diff --git a/tests/src/test_report_manager.cpp b/tests/src/test_report_manager.cpp
index f4ce184..55b5a3c 100644
--- a/tests/src/test_report_manager.cpp
+++ b/tests/src/test_report_manager.cpp
@@ -121,6 +121,19 @@
     EXPECT_THAT(path, Eq(reportMock.getPath()));
 }
 
+TEST_F(TestReportManager, addOnChangeReport)
+{
+    EXPECT_CALL(reportFactoryMock, convertMetricParams(_, _));
+    reportFactoryMock
+        .expectMake(reportParams.reportingType(ReportingType::onChange),
+                    Ref(*sut), Ref(storageMock))
+        .WillOnce(Return(ByMove(std::move(reportMockPtr))));
+
+    auto [ec, path] = addReport(reportParams);
+    EXPECT_THAT(ec.value(), Eq(boost::system::errc::success));
+    EXPECT_THAT(path, Eq(reportMock.getPath()));
+}
+
 TEST_F(TestReportManager, nameIsUsedToGenerateIdWhenIdIsEmptyInAddReport)
 {
     reportParams.reportId("ReportName");
diff --git a/tests/src/test_sensor.cpp b/tests/src/test_sensor.cpp
index c073ee7..8738bde 100644
--- a/tests/src/test_sensor.cpp
+++ b/tests/src/test_sensor.cpp
@@ -147,12 +147,9 @@
     ASSERT_TRUE(DbusEnvironment::waitForFuture("notify"));
 }
 
-TEST_F(TestSensorNotification, notifiesListenerWithValueWhenNoChangeOccurs)
+TEST_F(TestSensorNotification, doesntNotifyListenerWhenNoChangeOccurs)
 {
-    InSequence seq;
-
-    EXPECT_CALL(*listenerMock, sensorUpdated(Ref(*sut), Ge(timestamp), 42.7));
-    EXPECT_CALL(*listenerMock, sensorUpdated(Ref(*sut), Ge(timestamp)))
+    EXPECT_CALL(*listenerMock, sensorUpdated(Ref(*sut), Ge(timestamp), 42.7))
         .WillOnce(InvokeWithoutArgs(DbusEnvironment::setPromise("notify")));
 
     sensorObject->setValue(42.7);