Skip to content

Commit 09fc8b9

Browse files
rafalmaciagclaude
andcommitted
Add unit tests for EventAggregator event handler starter
4 tests covering: - Event delivery with correct SourceStreamId/RecipientId - Multiple event type subscriptions - StreamNameFromEventConvention usage (not handler convention) - DI registration via AddEventHandlerWithEventAggregatorSource Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a1c5a8b commit 09fc8b9

3 files changed

Lines changed: 184 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using FluentAssertions;
2+
using MicroPlumberd;
3+
using MicroPlumberd.Services.EventAggregator;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using ModelingEvolution.EventAggregator;
6+
7+
namespace MicroPlumberd.Services.EventAggregator.Tests;
8+
9+
public record TestEvent(string Message);
10+
public record AnotherEvent(int Value);
11+
12+
[OutputStream("TestHandler")]
13+
public class TestHandler : IEventHandler, ITypeRegister
14+
{
15+
public List<(Metadata Metadata, object Event)> ReceivedEvents { get; } = new();
16+
17+
public Task Handle(Metadata m, object ev)
18+
{
19+
ReceivedEvents.Add((m, ev));
20+
return Task.CompletedTask;
21+
}
22+
23+
public static IEnumerable<Type> Types { get; } = [typeof(TestEvent), typeof(AnotherEvent)];
24+
}
25+
26+
public class EventAggregatorEventHandlerStarterTests
27+
{
28+
private ServiceProvider BuildServiceProvider()
29+
{
30+
var services = new ServiceCollection();
31+
32+
services.AddLogging();
33+
services.AddSingleton(_ => PlumberEngine.Create(configure: cfg =>
34+
{
35+
// no-op, just need conventions
36+
}));
37+
services.AddScoped(_ => OperationContext.Create(Flow.Component));
38+
39+
// EventAggregator infrastructure
40+
services.AddSingleton<EventAggregatorPool>();
41+
services.AddSingleton<IEventAggregatorPool>(sp => sp.GetRequiredService<EventAggregatorPool>());
42+
services.AddSingleton<IEventAggregatorForwarder, NullForwarder>();
43+
services.AddScoped<IEventAggregator, ModelingEvolution.EventAggregator.EventAggregator>();
44+
45+
// Handler as singleton so we can observe it across scopes
46+
services.AddSingleton<TestHandler>();
47+
48+
// Starter registration (uses the public extension method)
49+
services.AddEventHandlerWithEventAggregatorSource<TestHandler, Guid>();
50+
51+
return services.BuildServiceProvider();
52+
}
53+
54+
[Fact]
55+
public async Task Handler_receives_event_with_correct_metadata()
56+
{
57+
await using var sp = BuildServiceProvider();
58+
59+
var starter = sp.GetRequiredService<EventAggregatorEventHandlerStarter<TestHandler, Guid>>();
60+
await starter.Start(CancellationToken.None);
61+
62+
var pool = sp.GetRequiredService<IEventAggregatorPool>();
63+
var recipientId = Guid.NewGuid();
64+
var evt = new TestEvent("hello");
65+
await pool.Broadcast(recipientId, evt);
66+
67+
await Task.Delay(200);
68+
69+
var handler = sp.GetRequiredService<TestHandler>();
70+
handler.ReceivedEvents.Should().HaveCount(1);
71+
var (metadata, receivedEvent) = handler.ReceivedEvents[0];
72+
receivedEvent.Should().Be(evt);
73+
74+
// SourceStreamId should contain the recipientId, parsable by StreamId<T>()
75+
metadata.SourceStreamId.Should().Contain(recipientId.ToString());
76+
// Parse the id from SourceStreamId the same way StreamId<T>() does
77+
var idx = metadata.SourceStreamId.IndexOf('-');
78+
var parsedId = Guid.Parse(metadata.SourceStreamId[(idx + 1)..]);
79+
parsedId.Should().Be(recipientId);
80+
}
81+
82+
[Fact]
83+
public async Task Handler_receives_multiple_event_types()
84+
{
85+
await using var sp = BuildServiceProvider();
86+
87+
var starter = sp.GetRequiredService<EventAggregatorEventHandlerStarter<TestHandler, Guid>>();
88+
await starter.Start(CancellationToken.None);
89+
90+
var pool = sp.GetRequiredService<IEventAggregatorPool>();
91+
var id = Guid.NewGuid();
92+
await pool.Broadcast(id, new TestEvent("first"));
93+
await pool.Broadcast(id, new AnotherEvent(42));
94+
95+
await Task.Delay(200);
96+
97+
var handler = sp.GetRequiredService<TestHandler>();
98+
handler.ReceivedEvents.Should().HaveCount(2);
99+
handler.ReceivedEvents[0].Event.Should().BeOfType<TestEvent>();
100+
handler.ReceivedEvents[1].Event.Should().BeOfType<AnotherEvent>();
101+
}
102+
103+
[Fact]
104+
public async Task SourceStreamId_uses_event_convention_not_handler()
105+
{
106+
await using var sp = BuildServiceProvider();
107+
108+
var starter = sp.GetRequiredService<EventAggregatorEventHandlerStarter<TestHandler, Guid>>();
109+
await starter.Start(CancellationToken.None);
110+
111+
var pool = sp.GetRequiredService<IEventAggregatorPool>();
112+
var recipientId = Guid.NewGuid();
113+
await pool.Broadcast(recipientId, new TestEvent("test"));
114+
115+
await Task.Delay(200);
116+
117+
var handler = sp.GetRequiredService<TestHandler>();
118+
handler.ReceivedEvents.Should().HaveCount(1);
119+
var sourceStreamId = handler.ReceivedEvents[0].Metadata.SourceStreamId;
120+
// TestEvent has no [OutputStream], so StreamNameFromEventConvention uses namespace
121+
// The category should NOT be "TestHandler" (that's the handler's convention)
122+
sourceStreamId.Should().NotStartWith("TestHandler",
123+
"category should come from event convention, not handler");
124+
sourceStreamId.Should().Contain(recipientId.ToString());
125+
}
126+
127+
[Fact]
128+
public async Task AddEventHandlerWithEventAggregatorSource_registers_all_services()
129+
{
130+
var services = new ServiceCollection();
131+
services.AddLogging();
132+
services.AddSingleton(_ => PlumberEngine.Create());
133+
services.AddScoped(_ => OperationContext.Create(Flow.Component));
134+
services.AddSingleton<EventAggregatorPool>();
135+
services.AddSingleton<IEventAggregatorPool>(sp => sp.GetRequiredService<EventAggregatorPool>());
136+
services.AddSingleton<IEventAggregatorForwarder, NullForwarder>();
137+
services.AddScoped<IEventAggregator, ModelingEvolution.EventAggregator.EventAggregator>();
138+
139+
services.AddEventHandlerWithEventAggregatorSource<TestHandler, Guid>();
140+
141+
await using var sp = services.BuildServiceProvider();
142+
143+
// Starter should be resolvable
144+
var starter = sp.GetService<EventAggregatorEventHandlerStarter<TestHandler, Guid>>();
145+
starter.Should().NotBeNull();
146+
147+
// Handler should be resolvable from scope
148+
using var scope = sp.CreateScope();
149+
var handler = scope.ServiceProvider.GetService<TestHandler>();
150+
handler.Should().NotBeNull();
151+
}
152+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
<Nullable>enable</Nullable>
7+
<IsPackable>false</IsPackable>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="coverlet.collector" Version="6.0.4" />
12+
<PackageReference Include="FluentAssertions" Version="6.12.1" />
13+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
14+
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.0" />
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
16+
<PackageReference Include="NSubstitute" Version="5.1.0" />
17+
<PackageReference Include="xunit" Version="2.9.3" />
18+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="..\MicroPlumberd.Services.EventAggregator\MicroPlumberd.Services.EventAggregator.csproj" />
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<Using Include="Xunit" />
27+
</ItemGroup>
28+
29+
</Project>

src/MicroPlumberd.Services.EventAggregator/ContainerExtensions.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
using System.Runtime.CompilerServices;
12
using MicroPlumberd;
23
using MicroPlumberd.Services;
34
using Microsoft.Extensions.DependencyInjection;
45
using Microsoft.Extensions.DependencyInjection.Extensions;
56

7+
[assembly: InternalsVisibleTo("MicroPlumberd.Services.EventAggregator.Tests")]
8+
69
namespace MicroPlumberd.Services.EventAggregator;
710

811
/// <summary>

0 commit comments

Comments
 (0)