Test metrics emitted by your code
Use MetricsTestkit to run code against an in-memory metrics backend and assert the exported metrics.
1. Add the testkit dependency
Add the testkit to the test scope in build.sbt:
libraryDependencies +=
"org.typelevel" %% "otel4s-oteljava-testkit" % "1.1.0" % Test
Add the test dependency to the test source file:
//> using test.dep "org.typelevel::otel4s-oteljava-testkit:1.1.0"
2. Allocate the instruments once
Group instruments in a value that can be created at application startup and passed to the code that records metrics.
Accepting a MeterProvider lets the test allocate the same instruments with the in-memory provider.
import cats.effect.IO
import org.typelevel.otel4s.metrics.{Counter, MeterProvider}
case class ServiceMetrics(
requests: Counter[IO, Long]
)
object ServiceMetrics {
def create(meterProvider: MeterProvider[IO]): IO[ServiceMetrics] =
for {
meter <- meterProvider.get("service")
requests <- meter.counter[Long]("service.requests").create
} yield ServiceMetrics(requests)
}
final class Service(metrics: ServiceMetrics) {
def handleRequest: IO[Unit] =
metrics.requests.inc()
}
3. Define an assertion helper
The expectation API returns structured mismatches. Convert them into the failure type used by your test framework.
import io.opentelemetry.sdk.metrics.data.MetricData
import org.typelevel.otel4s.oteljava.testkit.metrics.{MetricExpectation, MetricExpectations}
def assertMetrics(metrics: List[MetricData], expected: MetricExpectation*): IO[Unit] =
MetricExpectations.checkAll(metrics, expected: _*) match {
case Right(_) =>
IO.unit
case Left(mismatches) =>
IO.raiseError(new AssertionError(MetricExpectations.format(mismatches)))
}
This helper uses IO.raiseError so it remains independent of a specific test framework. Replace it with the framework's
failure mechanism when appropriate.
4. Run the code with MetricsTestkit
Create the testkit as a Resource, allocate ServiceMetrics once with its meterProvider, and use those instruments
throughout the test. Collect and assert the exported metrics before the resource closes.
import org.typelevel.otel4s.oteljava.testkit.metrics.MetricsTestkit
def test: IO[Unit] =
MetricsTestkit.inMemory[IO]().use { testkit =>
for {
serviceMetrics <- ServiceMetrics.create(testkit.meterProvider)
service = new Service(serviceMetrics)
_ <- service.handleRequest
_ <- service.handleRequest
metrics <- testkit.collectMetrics
_ <- assertMetrics(
metrics,
MetricExpectation.sum[Long]("service.requests").value(2L)
)
} yield ()
}
MetricExpectation ignores unspecified fields, such as timestamps and resource attributes.
What's next
- Verify metric metadata and required attributes against a generated semantic convention: Test metrics against semantic conventions
- For point attributes, histograms, collection-wide constraints, and custom numeric comparison, see the Metrics testkit reference.