TL;DR The Mock StatsD library simplifies the testing process for Kotlin/Java applications that send metrics to a StatsD server. It provides detailed verification capabilities for all types of StatsD metrics and automates port management for ease of use. It integrates smoothly with JUnit 5 for effortless unit testing and leverages Kotlin features for an idiomatic and intuitive experience. Ultimately, Mock StatsD promotes software quality and reliability by enabling thorough and efficient metrics testing.
StatsD is a simple daemon for easy stats aggregation which is commonly used for monitoring applications. The basic idea is to send different types of metrics (like counters, timers, gauges) from your application to StatsD, which then periodically aggregates the metrics and pushes them to Graphite (or some other defined backend).
The Mock StatsD library offers several benefits for Kotlin and Java developers, especially those working on applications that send metrics to a StatsD server:
-
Simplified Testing: By providing a mock StatsD server, this tool makes it easier to write unit tests for your application's metrics-related functionality. Instead of having to manage a real StatsD server for testing purposes, you can simply start and stop the mock server as needed.
-
Detailed Verification: Mock StatsD allows you to verify the exact metrics sent by your application, including the type, name, and value of each metric. This makes it possible to catch subtle bugs in your metrics code.
-
Support for All StatsD Metric Types: Whether your application uses counters, timers, gauges, or histograms, you can test them all with Mock StatsD.
-
Automatic Port Management: If you don't specify a port number when creating the mock server, it automatically selects an available port. This is especially useful in environments where many tests are running concurrently and may be using network resources.
-
Easy Integration with JUnit 5: The library provides a JUnit 5 extension, which automatically manages the lifecycle of the mock server. This means the server will start before your tests run and stop after they complete, minimizing the boilerplate code in your tests.
-
Built with Kotlin: As a Kotlin library, Mock StatsD integrates seamlessly with your Kotlin codebase. It takes advantage of Kotlin features like extension functions, and its API is designed to be idiomatic and intuitive for Kotlin developers.
By simplifying the process of testing your application's metrics code and providing detailed, easy-to-use verification methods, Mock StatsD helps ensure the quality and reliability of your software.
-
Add dependency in
pom.xml
:<dependency> <groupId>com.github.kpavlov.mocks.statsd</groupId> <artifactId>mock-statsd</artifactId> <version>${mock-statsd.version}</version> <scope>test</scope> </dependency>
or in
build.gradle.kts
:dependencies { testImplementation("com.github.kpavlov.mocks.statsd:mock-statsd:$mockStatsdVersion") }
Check latest version in Maven Central repository
-
To use this library, add the following import statement to your Kotlin file:
import me.kpavlov.mocks.statsd.server.MockStatsDServer
-
Create a new instance of
MockStatsDServer
with, specifying the port number. UseRANDOM_PORT
to automatically select an available port:val mockStatsD = MockStatsDServer(RANDOM_PORT)
-
Start the server:
mockStatsD.start()
You can now send metrics to the server and then verify that they were received correctly.
-
Cleaning Up
mockStatsD.reset()
Cleans up collected metrics and recorded calls.
-
Stopping server
When you're done with the server, stop it with the
stop
method:mockStatsD.stop()
This ensures that the port used by the server is freed up and can be used by other processes.
You can also register MockStatsDServer as JUnit 5 extension.
It will automatically create and start a single instance of MockStatsDServer
to use in all tests.
It will be stopped on JVM shutdown, when test execution is finished.
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(StatsDJUnit5Extension::class)
internal class Junit5ExtensionTest : BaseStatsDServerTest() {
lateinit var mockStatsD: MockStatsDServer
lateinit var client: StatsDClient
@BeforeAll
fun beforeAll() {
mockStatsD = StatsDJUnit5Extension.statsDServer()
client = StatsDClient(port = mockStatsD.port())
}
}
Now you can use MockStatsD
server and statsD client in tests.
The server can capture different types of metrics: Time, Counter, Gauge, and Histogram. Here's how to send each type of metric:
val name = "timeMetric"
val value = 31L
client.time(name, value)
val name = "counterMetric"
client.incrementCounter(name) // increment by 1
client.incrementCounter(name, 3) // increment by 3
client.incrementCounter(name, -2) // decrement by 2
client.decrementCounter(name) // decrement by 1
val name = "gaugeMetric"
val value = 42.0
client.gauge(name, value)
val name = "histogramMetric"
val value = 42.0
client.histogram(name, value)
val name = "histogramMetric"
client.set(name, 42.0)
client.set(name, 128.0)
client.set(name, 42.0)
println(mockStatsD.metricContents(name)?.asList()?.sorted()) // [42.0, 128.0]
MockStatsD supports receiving multiple metrics in a single packet by separating them with a newline characters (\n
).
batchGauge:333.0|g\nbatchCounter:42.0|c
Each message could therefore represent a batch of metrics sent from the client in a single UDP packet. This is useful for reducing network load when sending multiple metrics, as it can all be done in one network operation.
The batch support is achieved by first splitting the batched message into individual metrics and then handling each metric separately, e.g.:
@Test
fun `Server should handle multi-metric packets`() {
val gaugeName = "batchGauge"
val counterName = "batchCounter"
// Set initial value
val gaugeValue = 333.0
val counterValue = 42.0
client.send("$gaugeName:$gaugeValue|g\n$counterName:$counterValue|c")
await untilAsserted {
assertThat(statsd.metric(gaugeName)).isEqualTo(gaugeValue)
}
await untilAsserted {
assertThat(statsd.metric(counterName)).isEqualTo(counterValue)
}
}
After sending metrics, you can verify that the server captured them correctly. Use the metric
method to retrieve a metric value, and verifyCall
to verify that a specific call was made. For example:
await untilAsserted {
assertThat(mockStatsD.metric(name)).isEqualTo(value.toDouble())
}
println(mockStatsD.calls()) // prints all calls to console
mockStatsD.verifyCall("$name:$value|ms")
mockStatsD.verifyNoMoreCalls("$name:$value|ms")
This example checks that the server received a Time metric with the specified name and value, and that no more calls with the same name and value were made.
Check out the StatsDServerTest.kt
file in the test
directory for a complete example of how to use the StatsDServer
. This test class demonstrates how to set up a server, send different types of metrics, verify the captured metrics, and clean up the server.