Skip to content

chore: centralize test mocking and add named helpers - #63

Merged
yordis merged 1 commit into
mainfrom
chore-testing
Mar 13, 2026
Merged

yordis merged 1 commit into
mainfrom
chore-testing

Conversation

@yordis

@yordis yordis commented Mar 13, 2026

Copy link
Copy Markdown
Member

Summary

Reduces test noise by centralizing MockEventStore setup and replacing inline expect/stub calls with named helper functions that document intent.

Changes

New infrastructure

  • MockEventStoreCase — Now stubs all 10 adapter callbacks (subscribe, append_to_stream, stream_forward, read_snapshot, etc.) with sensible defaults
  • MockProjectionCase — New case for projection tests; removes duplicated stub_event_store/1 from error_callback_test and runtime_config_projector_test
  • MockEventStoreHelpers — Named helpers for common scenarios:
    • expect_wrong_expected_version_conflict/1
    • expect_successful_append_with_empty_stream/1
    • expect_open_aggregate/1
    • expect_concurrency_retry_succeeds/2
    • expect_too_many_retry_attempts/2
    • expect_wrong_expected_version_retry_succeeds/3
    • expect_stream_not_found/1, expect_stream_with_events/3
    • stub_subscribe_to_return_handler/0
    • build_recorded_event/4

Refactored tests

  • aggregate_concurrency_test.exs
  • aggregate_telemetry_test.exs
  • opentelemetry/aggregate_test.exs
  • event_handler_after_start_test.exs
  • error_callback_test.exs (migrated to MockProjectionCase)
  • runtime_config_projector_test.exs (migrated to MockProjectionCase)

Cleanup

  • Removed unused mocks: MockRouter, Application.Mock, TypeProvider.Mock
  • Removed redundant MockEventStore alias from opentelemetry/aggregate_test.exs

@cursor

cursor Bot commented Mar 13, 2026 •

Copy link
Copy Markdown

PR Summary

Low Risk
Low risk: changes are confined to test support code and test refactors, with no production behavior changes. Main risk is brittle tests if helper expectations don’t exactly match prior inline mocks.

Overview
Refactors tests to centralize MockEventStore stubbing/expectations, replacing repeated inline Mox.stub/expect blocks with named helpers in the new MockEventStoreHelpers module.

Introduces MockProjectionCase to standardize projection test setup (including sandbox + subscription behavior) and migrates projection tests to it; adds a small after_update/3 callback in RuntimeConfigProjector to assert post-persist behavior.

Cleans up unused mocks and simplifies several aggregate/telemetry/opentelemetry tests to use the new helper functions and recorded-event builder.

Written by Cursor Bugbot for commit ea13be5. This will update automatically on new commits. Configure here.

@coderabbitai

coderabbitai Bot commented Mar 13, 2026 •

Copy link
Copy Markdown

Walkthrough

Refactors test suites to replace inline Mox expectations with centralized test helpers and case templates. Adds Commanded.TestSupport.MockEventStoreHelpers and Commanded.MockProjectionCase, updates numerous tests to use the helpers, and introduces a small projection callback used in tests.

Changes

Cohort / File(s) Summary
New Test Helpers & Case Template
test/support/mock_event_store_helpers.ex, test/support/mock_projection_case.ex
Adds MockEventStoreHelpers (many helper functions for stubbing/expecting EventStore interactions and building RecordedEvent fixtures) and MockProjectionCase (ExUnit.CaseTemplate wiring Mox, stubbing event store, sandbox setup).
Test support integration
test/support/mock_event_store_case.ex, test/support/mocks.ex
Imports and uses MockEventStoreHelpers in existing case helper; replaces explicit stubs with stub_common_event_store/0; removes three previously defined mocks from mocks.ex.
Aggregate tests
test/aggregates/aggregate_concurrency_test.exs, test/aggregates/aggregate_telemetry_test.exs
Replaces per-test MockEventStore expect/stub sequences with higher-level helpers (e.g., expect_concurrency_retry_succeeds, expect_stream_with_events) and uses build_recorded_event/3 to construct events.
OpenTelemetry & event handler tests
test/opentelemetry/aggregate_test.exs, test/event_handler_after_start_test.exs
Removes inline Mox imports and MockEventStore aliases; delegates subscribe/append/stream expectations to helper functions (e.g., expect_wrong_expected_version_conflict).
Projection tests migration
test/projections/error_callback_test.exs, test/projections/runtime_config_projector_test.exs
Switches tests to Commanded.MockProjectionCase, removing manual TestApplication startup, sandbox checkout, and inline EventStore stubs; asserts projection-produced messages where applicable.
Projection helper & runtime projector
test/support/runtime_config_projector.ex
Adds after_update/3 callback to RuntimeConfigProjector used by tests to send {:projected, name} to a pid.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I stitched the mocks into one neat kit,

helpers and cases make tests nicely fit.
No more scattered stubs to chase,
the warren hops with tidy grace —
carrots for clarity, one small happy bit.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively covers the changes made, including new infrastructure (MockEventStoreCase, MockProjectionCase, MockEventStoreHelpers), refactored tests, and cleanup work.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'chore: centralize test mocking and add named helpers' accurately summarizes the primary change: consolidating MockEventStore setup into centralized helpers and introducing named helper functions for test scenarios.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore-testing
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
test/support/mock_event_store_helpers.ex (3)

170-177: Consider documenting the default count.

The default count: 6 presumably matches the aggregate's retry limit configuration. A brief note in the @doc explaining this coupling would help maintainers understand why this specific default was chosen.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/support/mock_event_store_helpers.ex` around lines 170 - 177, Update the
`@doc` for expect_too_many_retry_attempts to explicitly state that the default
count is 6 and that this value is chosen to match the aggregate's retry limit
configuration (so callers know the coupling); mention that the count can be
overridden via the opts parameter (e.g., opts[:count]) and that the function
calls expect_append_wrong_expected_version and expect_stream_empty with that
count so both mocks use the same retry attempt number.

199-200: Fallback event_type may be misleading.

For non-struct data, returning "Elixir.Commanded.EventStore.RecordedEvent" as the event type is semantically confusing—it suggests the event is a RecordedEvent rather than indicating an unknown/untyped event. Consider a clearer fallback:

♻️ Suggested alternatives
  defp infer_event_type(%{__struct__: struct}), do: to_string(struct)
- defp infer_event_type(_), do: "Elixir.Commanded.EventStore.RecordedEvent"
+ defp infer_event_type(_), do: "UnknownEvent"

Or require an explicit :event_type in opts for non-struct data.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/support/mock_event_store_helpers.ex` around lines 199 - 200, The
fallback in infer_event_type currently returns the misleading
"Elixir.Commanded.EventStore.RecordedEvent" for non-structs; change
infer_event_type to accept an optional opts (e.g., infer_event_type(data, opts
\\ [])) and return opts[:event_type] when provided, otherwise use a clear
default like "Elixir.UnknownEvent" (or "unknown") instead of
"Elixir.Commanded.EventStore.RecordedEvent"; update callers to pass opts where
non-struct event_type must be explicit.

71-75: Consider adding count option for API consistency.

Unlike expect_stream_empty/2 and other helpers, this function lacks a count option. For consistency and flexibility:

♻️ Suggested refactor
-  def expect_stream_not_found(stream_uuid) do
-    expect(MockEventStore, :stream_forward, fn _meta, ^stream_uuid, _from, _batch_size ->
+  def expect_stream_not_found(stream_uuid, opts \\ []) do
+    count = Keyword.get(opts, :count, 1)
+
+    expect(MockEventStore, :stream_forward, count, fn _meta, ^stream_uuid, _from, _batch_size ->
       {:error, :stream_not_found}
     end)
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/support/mock_event_store_helpers.ex` around lines 71 - 75, The helper
expect_stream_not_found/1 is missing a count option for consistency; change it
to accept an optional count argument (e.g., def
expect_stream_not_found(stream_uuid, count \\ 0)) and use that count in the
expect for MockEventStore.stream_forward by matching the _batch_size argument to
the provided count (pin the count variable in the anonymous function head), and
update any callers to pass a count where needed so behavior matches
expect_stream_empty/2 and other helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@test/support/mock_projection_case.ex`:
- Around line 35-37: The stub for MockEventStore.subscribe_to returns {:ok,
self()} but never sends the subscription acknowledgement message to the handler;
update the stub for subscribe_to (MockEventStore.subscribe_to) to generate a
reference (e.g., ref = make_ref()) and send {:subscribed, ref} to the handler
process argument before returning {:ok, self()} so projection handlers receive
the subscription lifecycle message they expect.

---

Nitpick comments:
In `@test/support/mock_event_store_helpers.ex`:
- Around line 170-177: Update the `@doc` for expect_too_many_retry_attempts to
explicitly state that the default count is 6 and that this value is chosen to
match the aggregate's retry limit configuration (so callers know the coupling);
mention that the count can be overridden via the opts parameter (e.g.,
opts[:count]) and that the function calls expect_append_wrong_expected_version
and expect_stream_empty with that count so both mocks use the same retry attempt
number.
- Around line 199-200: The fallback in infer_event_type currently returns the
misleading "Elixir.Commanded.EventStore.RecordedEvent" for non-structs; change
infer_event_type to accept an optional opts (e.g., infer_event_type(data, opts
\\ [])) and return opts[:event_type] when provided, otherwise use a clear
default like "Elixir.UnknownEvent" (or "unknown") instead of
"Elixir.Commanded.EventStore.RecordedEvent"; update callers to pass opts where
non-struct event_type must be explicit.
- Around line 71-75: The helper expect_stream_not_found/1 is missing a count
option for consistency; change it to accept an optional count argument (e.g.,
def expect_stream_not_found(stream_uuid, count \\ 0)) and use that count in the
expect for MockEventStore.stream_forward by matching the _batch_size argument to
the provided count (pin the count variable in the anonymous function head), and
update any callers to pass a count where needed so behavior matches
expect_stream_empty/2 and other helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a35dc7bb-7215-4cd2-afb5-081dbd8b5426

📥 Commits

Reviewing files that changed from the base of the PR and between 590e11d and 821d56a.

📒 Files selected for processing (10)
  • test/aggregates/aggregate_concurrency_test.exs
  • test/aggregates/aggregate_telemetry_test.exs
  • test/event_handler_after_start_test.exs
  • test/opentelemetry/aggregate_test.exs
  • test/projections/error_callback_test.exs
  • test/projections/runtime_config_projector_test.exs
  • test/support/mock_event_store_case.ex
  • test/support/mock_event_store_helpers.ex
  • test/support/mock_projection_case.ex
  • test/support/mocks.ex
💤 Files with no reviewable changes (1)
  • test/support/mocks.ex

Comment thread test/support/mock_projection_case.ex Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Comment thread test/support/mock_projection_case.ex
@yordis
yordis force-pushed the chore-testing branch 2 times, most recently from 2ef05c9 to b233029 Compare March 13, 2026 21:05
Signed-off-by: Yordis Prieto <yordis.prieto@gmail.com>
@yordis yordis changed the title Centralize test mocking and add named helpers chore: centralize test mocking and add named helpers Mar 13, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/support/mock_event_store_helpers.ex (1)

181-243: Consider parameterizing version numbers in expect_concurrency_retry_succeeds/2.

The helper hardcodes versions 1, 2, 2 for the append/stream sequence. While this works for the current test cases, adding optional :expected_version and :reload_version parameters (similar to expect_wrong_expected_version_retry_succeeds/3) would make the helper more flexible for future scenarios.

The :counters usage for stateful mock behavior in expect_wrong_expected_version_retry_succeeds/3 is a clever approach that keeps the expectation self-contained.

♻️ Optional: Add version parameters for flexibility
-  def expect_concurrency_retry_succeeds(stream_uuid, concurrent_event) do
-    expect(MockEventStore, :append_to_stream, fn _meta, ^stream_uuid, 1, _events, _opts ->
+  def expect_concurrency_retry_succeeds(stream_uuid, concurrent_event, opts \\ []) do
+    expected_version = Keyword.get(opts, :expected_version, 1)
+    reload_version = Keyword.get(opts, :reload_version, 2)
+
+    expect(MockEventStore, :append_to_stream, fn _meta, ^stream_uuid, ^expected_version, _events, _opts ->
       {:error, :wrong_expected_version}
     end)

-    expect(MockEventStore, :stream_forward, fn _meta, ^stream_uuid, 2, _batch_size ->
+    expect(MockEventStore, :stream_forward, fn _meta, ^stream_uuid, ^reload_version, _batch_size ->
       [concurrent_event]
     end)

-    expect(MockEventStore, :append_to_stream, fn _meta, ^stream_uuid, 2, _events, _opts ->
+    expect(MockEventStore, :append_to_stream, fn _meta, ^stream_uuid, ^reload_version, _events, _opts ->
       :ok
     end)
   end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/support/mock_event_store_helpers.ex` around lines 181 - 243, The helper
expect_concurrency_retry_succeeds/2 hardcodes versions (1, 2, 2); make it accept
optional version parameters (e.g., opts with :expected_version and
:reload_version) defaulting to the current values and use those variables in the
MockEventStore expect clauses instead of literals so the append_to_stream and
stream_forward patterns match the supplied versions; update the function head
and docstring to mention the new opts and default values so tests can pass
custom expected/reload versions when needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/support/mock_event_store_helpers.ex`:
- Around line 181-243: The helper expect_concurrency_retry_succeeds/2 hardcodes
versions (1, 2, 2); make it accept optional version parameters (e.g., opts with
:expected_version and :reload_version) defaulting to the current values and use
those variables in the MockEventStore expect clauses instead of literals so the
append_to_stream and stream_forward patterns match the supplied versions; update
the function head and docstring to mention the new opts and default values so
tests can pass custom expected/reload versions when needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef28da4f-1cf6-47aa-bdbb-1cde66e8b9a7

📥 Commits

Reviewing files that changed from the base of the PR and between 821d56a and ea13be5.

📒 Files selected for processing (11)
  • test/aggregates/aggregate_concurrency_test.exs
  • test/aggregates/aggregate_telemetry_test.exs
  • test/event_handler_after_start_test.exs
  • test/opentelemetry/aggregate_test.exs
  • test/projections/error_callback_test.exs
  • test/projections/runtime_config_projector_test.exs
  • test/support/mock_event_store_case.ex
  • test/support/mock_event_store_helpers.ex
  • test/support/mock_projection_case.ex
  • test/support/mocks.ex
  • test/support/runtime_config_projector.ex
💤 Files with no reviewable changes (1)
  • test/support/mocks.ex
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/aggregates/aggregate_concurrency_test.exs
  • test/support/mock_projection_case.ex
  • test/event_handler_after_start_test.exs
  • test/support/mock_event_store_case.ex

@yordis
yordis merged commit c4d1d97 into main Mar 13, 2026
7 checks passed
@yordis
yordis deleted the chore-testing branch March 13, 2026 21:39
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.

1 participant