Skip to content

Setting a first test foundation for Metrics event#1439

Open
firasrg wants to merge 4 commits intodevelopfrom
firasrg/setting-analytics-tests
Open

Setting a first test foundation for Metrics event#1439
firasrg wants to merge 4 commits intodevelopfrom
firasrg/setting-analytics-tests

Conversation

@firasrg
Copy link
Copy Markdown
Contributor

@firasrg firasrg commented Mar 13, 2026

What

Adds the first unit test for the analytics system.

  • Added MetricsTests to verify Metrics#count(...) persists a metric event;
  • Asserted both the stored event name and the recorded timestamp;
  • Added async waiting/timeout handling for reliable verification of background writes;
  • Exposed the Metrics executor service so tests can shut it down cleanly;
  • Enabled debug logging for org.togetherjava.tjbot in test runs;
  • Added AssertJ for test assertions

Why

Metrics performs asynchronous database writes, so having direct test coverage here improves confidence in analytics persistence and makes future refactors safer.

Validation

  • ran :application:test --tests org.togetherjava.tjbot.features.MetricsTests

@firasrg firasrg requested a review from a team as a code owner March 13, 2026 18:34
@sonarqubecloud
Copy link
Copy Markdown

@firasrg firasrg marked this pull request as draft March 13, 2026 19:25
Comment thread application/src/test/resources/log4j2.xml Outdated
Comment thread application/build.gradle Outdated
Comment on lines +56 to +58
public ExecutorService getExecutorService() {
return service;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

undo, this won't be needed (see other comment why)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not done yet)

import static org.assertj.core.api.Assertions.within;
import static org.junit.jupiter.api.Assertions.fail;

final class MetricsTests {
Copy link
Copy Markdown
Member

@Zabuzard Zabuzard Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test is too much in terms of complexity for no tangible benefit.

lets step back and create something simpler that beginners can understand and maintain while providing pretty much the same test-safety in terms of relevant coverage.

  1. create a package private method in Metrics called countBlocking. its identical to count but it won't use the executor service. so it creates the instant and then calls processEvent directly
  2. in ur unit test this will be the method ur testing against now. without the async stuff the test will simplify a lot
  3. the test should use a GWT structure (see other examples in the codebase, alternative patterns that share the same idea: 4-phases SEVT or AAA). try to aim for something like this:
@Test
void basicEventCount() {
  // GIVEN a test event
  String expectedEvent = "test";
  Instant expectedHappenedAt = Instant.now();

  // WHEN counting the event
  metrics.countBlocking(expectedEvent);

  // THEN the event was saved in the DB
  Foo entry = database...
  String actualEvent = entry.event();
  Instant actualHappenedAt = entry.happenedAt();

  assertEquals(expectedEvent, actualEvent);
  assertCloseEnough(expectedHappenedAt, actualHappenedAt);
} 

private static void assertCloseEnough(Instant expected, Instant actual) {
 ... // assert that its within 1min difference
} 

(double check the correct order of expected vs actual in assert, i never can remember it, lol)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the guide. However, i don't see this a good idea: creating new method inside the Metrics, while it's only for tests. Also putting such method in the test class, doesn't really test the real one count

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not meaningful to test the true count method. The async nature makes it very difficult to test, too difficult for what we want right now. It is more meaningful to instead split the method into parts so that your test can at least cover 98% of its code. Thats good and also standard practice.

Please do it, thanks.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(not done yet)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have to create an assertion method assertCloseEnough ?

@firasrg firasrg marked this pull request as ready for review April 27, 2026 20:21
@sonarqubecloud
Copy link
Copy Markdown

Comment on lines +57 to +66
/**
* Track an event execution with flag for async execution.
*
* @param event the event to save
* @param doAsync the async flag
*/
public void count(String event, boolean doAsync) {
count(event, Map.of(), doAsync);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove that, we dont want to expose that doAsync stuff to the outside. ur test can use the void count(String event, Map<String, Object> dimensions, boolean doAsync) {, tests are in the same package so package-private works for them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by ??

tests are in the same package so package-private works for them.

Comment on lines +48 to +51
* Track an event execution with dimensions provided.
*
* @param event the event to save
* @param dimensions the dimensions to save
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please dont change the javadoc, it was this:

     * Track an event execution with additional contextual data.
     *
     * @param event the name of the event to record (e.g. "user_signup", "purchase")
     * @param dimensions optional key-value pairs providing extra context about the event. These are
     *        often referred to as "metadata" and can include things like: userId: "12345", name:
     *        "John Smith", channel_name: "chit-chat" etc. This data helps with filtering, grouping,
     *        and analyzing events later. Note: A value for a metric should be a Java primitive
     *        (String, int, double, long float).
     *

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was absolutely no javadoc

Comment on lines -75 to -80
/**
*
* @param event the event to save
* @param happenedAt the moment when the event is dispatched
* @param dimensionsJson optional JSON-serialized dimensions, or null
*/
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont remove that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok and I'll add a description

@firasrg firasrg changed the title Add initial unit test coverage for Metrics event persistence Setting a first test foundation for Metrics event Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants