Skip to content

Commit 12e88f5

Browse files
committed
Added the first Unit Tests and some xml description
1 parent c32182b commit 12e88f5

File tree

6 files changed

+210
-9
lines changed

6 files changed

+210
-9
lines changed

CodeChavez.M3diator.sln

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ VisualStudioVersion = 17.14.36301.6
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeChavez.M3diator", "src\CodeChavez.M3diator\CodeChavez.M3diator.csproj", "{2C864E76-AB72-4201-BD6F-EEBCF0BC9F95}"
77
EndProject
8+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
9+
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CodeChavez.Mediator.Tests", "tests\CodeChavez.Mediator.Tests\CodeChavez.Mediator.Tests.csproj", "{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF}"
11+
EndProject
812
Global
913
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1014
Debug|Any CPU = Debug|Any CPU
@@ -15,10 +19,17 @@ Global
1519
{2C864E76-AB72-4201-BD6F-EEBCF0BC9F95}.Debug|Any CPU.Build.0 = Debug|Any CPU
1620
{2C864E76-AB72-4201-BD6F-EEBCF0BC9F95}.Release|Any CPU.ActiveCfg = Release|Any CPU
1721
{2C864E76-AB72-4201-BD6F-EEBCF0BC9F95}.Release|Any CPU.Build.0 = Release|Any CPU
22+
{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23+
{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF}.Debug|Any CPU.Build.0 = Debug|Any CPU
24+
{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF}.Release|Any CPU.ActiveCfg = Release|Any CPU
25+
{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF}.Release|Any CPU.Build.0 = Release|Any CPU
1826
EndGlobalSection
1927
GlobalSection(SolutionProperties) = preSolution
2028
HideSolutionNode = FALSE
2129
EndGlobalSection
30+
GlobalSection(NestedProjects) = preSolution
31+
{FCAFBDB6-6DAD-462A-BFDD-DD578BBB5BCF} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
32+
EndGlobalSection
2233
GlobalSection(ExtensibilityGlobals) = postSolution
2334
SolutionGuid = {EF37B500-F02F-44DE-A9A0-D83C45675BCB}
2435
EndGlobalSection

src/CodeChavez.M3diator/CodeChavez.M3diator.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
<PackageId>CodeChavez.M3diator</PackageId>
1515
<Title>CodeChavez M3diator</Title>
1616
<Product>CodeChavez.M3diator</Product>
17-
<Version>0.1.2</Version>
17+
<Version>0.1.3</Version>
18+
<PackageTags>CodeChavez,Mediator,M3diator,Helpers,Utilities,.net</PackageTags>
1819
</PropertyGroup>
1920
<ItemGroup>
2021
<None Include="..\..\assets\imgs\M3diator_128x128.png" Pack="true" PackagePath="" />

src/CodeChavez.M3diator/M3diator.cs

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,32 @@
55

66
namespace CodeChavez.M3diator;
77

8+
/// <summary>
9+
/// M3diator implementation allow access to Handle and Notification handlers
10+
/// </summary>
811
public class M3diator : IM3diator
912
{
1013
private readonly IServiceProvider _serviceProvider;
1114
private static readonly ConcurrentDictionary<Type, Func<IServiceProvider, object, CancellationToken, ValueTask>> _voidHandlers = new();
1215
private static readonly ConcurrentDictionary<Type, Func<IServiceProvider, object, CancellationToken, ValueTask<object>>> _responseHandlers = new();
1316
private static readonly ConcurrentDictionary<Type, Func<IServiceProvider, object, CancellationToken, Task>> _notificationHandlers = new();
1417

18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="M3diator"/> class
20+
/// </summary>
21+
/// <param name="sp">Service Provider</param>
1522
public M3diator(IServiceProvider sp)
1623
{
1724
_serviceProvider = sp;
1825
}
1926

20-
// Handle commands/queries with responses
27+
/// <summary>
28+
/// Handles requests with Response
29+
/// </summary>
30+
/// <typeparam name="TResponse"></typeparam>
31+
/// <param name="request"></param>
32+
/// <param name="ct"></param>
33+
/// <returns></returns>
2134
public async Task<TResponse> Handle<TResponse>(IRequest<TResponse> request, CancellationToken ct = default)
2235
{
2336
var handlerFunc = _responseHandlers.GetOrAdd(request.GetType(), static reqType =>
@@ -32,16 +45,23 @@ public async Task<TResponse> Handle<TResponse>(IRequest<TResponse> request, Canc
3245
var getHandlerCall = Expression.Call(
3346
typeof(ServiceProviderServiceExtensions),
3447
nameof(ServiceProviderServiceExtensions.GetRequiredService),
35-
[handlerType],
48+
new[] { handlerType },
3649
spParam
3750
);
3851

3952
var castRequest = Expression.Convert(reqParam, reqType);
4053
var callHandle = Expression.Call(getHandlerCall, handleMethod, castRequest, ctParam);
4154

42-
var castResult = Expression.Convert(callHandle, typeof(object));
55+
// Convert Task<TResponse> to ValueTask<object>
56+
var wrapCall = Expression.Call(
57+
typeof(M3diator),
58+
nameof(WrapTaskAsValueTask),
59+
new[] { typeof(TResponse) },
60+
callHandle
61+
);
62+
4363
var lambda = Expression.Lambda<Func<IServiceProvider, object, CancellationToken, ValueTask<object>>>(
44-
Expression.Convert(castResult, typeof(ValueTask<object>)),
64+
wrapCall,
4565
spParam, reqParam, ctParam
4666
);
4767

@@ -52,7 +72,19 @@ public async Task<TResponse> Handle<TResponse>(IRequest<TResponse> request, Canc
5272
return (TResponse)result;
5373
}
5474

55-
// Handle commands with no response
75+
private static async ValueTask<object> WrapTaskAsValueTask<T>(Task<T> task)
76+
{
77+
var result = await task.ConfigureAwait(false);
78+
return result!;
79+
}
80+
81+
/// <summary>
82+
/// Handles requests with no response
83+
/// </summary>
84+
/// <typeparam name="TRequest"></typeparam>
85+
/// <param name="request"></param>
86+
/// <param name="ct"></param>
87+
/// <returns></returns>
5688
public async Task Handle<TRequest>(TRequest request, CancellationToken ct = default) where TRequest : IRequest
5789
{
5890
var handlerFunc = _voidHandlers.GetOrAdd(request.GetType(), static reqType =>
@@ -67,15 +99,23 @@ public async Task Handle<TRequest>(TRequest request, CancellationToken ct = defa
6799
var getHandlerCall = Expression.Call(
68100
typeof(ServiceProviderServiceExtensions),
69101
nameof(ServiceProviderServiceExtensions.GetRequiredService),
70-
[handlerType],
102+
new[] { handlerType },
71103
spParam
72104
);
73105

74106
var castRequest = Expression.Convert(reqParam, reqType);
75107
var callHandle = Expression.Call(getHandlerCall, handleMethod, castRequest, ctParam);
76108

109+
// Convert Task to ValueTask
110+
var wrapCall = Expression.Call(
111+
typeof(M3diator),
112+
nameof(WrapTaskAsValueTaskVoid),
113+
Type.EmptyTypes,
114+
callHandle
115+
);
116+
77117
var lambda = Expression.Lambda<Func<IServiceProvider, object, CancellationToken, ValueTask>>(
78-
Expression.Convert(callHandle, typeof(ValueTask)),
118+
wrapCall,
79119
spParam, reqParam, ctParam
80120
);
81121

@@ -84,7 +124,19 @@ public async Task Handle<TRequest>(TRequest request, CancellationToken ct = defa
84124

85125
await handlerFunc(_serviceProvider, request!, ct);
86126
}
87-
127+
128+
private static async ValueTask WrapTaskAsValueTaskVoid(Task task)
129+
{
130+
await task.ConfigureAwait(false);
131+
}
132+
133+
/// <summary>
134+
/// Publishes INotification
135+
/// </summary>
136+
/// <typeparam name="TNotification"></typeparam>
137+
/// <param name="notification"></param>
138+
/// <param name="ct"></param>
139+
/// <returns></returns>
88140
public async Task Publish<TNotification>(TNotification notification, CancellationToken ct = default)
89141
where TNotification : INotification
90142
{
@@ -99,4 +151,5 @@ public async Task Publish<TNotification>(TNotification notification, Cancellatio
99151

100152
await Task.WhenAll(tasks);
101153
}
154+
102155
}

src/CodeChavez.M3diator/Register/M3diatorModule.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@
44

55
namespace CodeChavez.M3diator.Register;
66

7+
/// <summary>
8+
/// Module use to register M3diator
9+
/// </summary>
710
public static class M3diatorModule
811
{
12+
/// <summary>
13+
/// Register All M3diator IRequestHandler, INotificationHandler, IM3diator
14+
/// </summary>
15+
/// <param name="services"></param>
16+
/// <param name="assemblies"></param>
17+
/// <returns></returns>
918
public static IServiceCollection AddM3diator(this IServiceCollection services, params Assembly[] assemblies)
1019
{
1120
services.AddSingleton<IM3diator, M3diator>();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.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+
<PrivateAssets>all</PrivateAssets>
13+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
14+
</PackageReference>
15+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
16+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
17+
<PackageReference Include="Moq" Version="4.20.72" />
18+
<PackageReference Include="xunit" Version="2.9.3" />
19+
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
20+
<PrivateAssets>all</PrivateAssets>
21+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
22+
</PackageReference>
23+
</ItemGroup>
24+
25+
<ItemGroup>
26+
<ProjectReference Include="..\..\src\CodeChavez.M3diator\CodeChavez.M3diator.csproj" />
27+
</ItemGroup>
28+
29+
<ItemGroup>
30+
<Using Include="Xunit" />
31+
</ItemGroup>
32+
33+
</Project>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using CodeChavez.M3diator.Interfaces;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Moq;
4+
5+
namespace CodeChavez.Mediator.Tests;
6+
7+
public class M3diatorTests
8+
{
9+
private readonly IServiceCollection _services;
10+
11+
public M3diatorTests()
12+
{
13+
_services = new ServiceCollection();
14+
}
15+
16+
[Fact]
17+
public async Task Handle_WithResponse_ReturnsExpectedResult()
18+
{
19+
// Arrange
20+
var request = new TestQuery();
21+
var expectedResponse = "Hello Mundo!";
22+
var handlerMock = new Mock<IRequestHandler<TestQuery, string>>();
23+
handlerMock.Setup(h => h.Handle(request, It.IsAny<CancellationToken>()))
24+
.ReturnsAsync(expectedResponse);
25+
26+
_services.AddScoped(_ => handlerMock.Object);
27+
var provider = _services.BuildServiceProvider();
28+
var mediator = new CodeChavez.M3diator.M3diator(provider);
29+
30+
// Act
31+
var result = await mediator.Handle<string>(request);
32+
33+
// Assert
34+
Assert.Equal(expectedResponse, result);
35+
}
36+
37+
[Fact]
38+
public async Task Handle_VoidCommand_CallsHandler()
39+
{
40+
// Arrange
41+
var request = new TestCommand();
42+
var handlerMock = new Mock<IRequestHandler<TestCommand>>();
43+
handlerMock.Setup(h => h.Handle(request, It.IsAny<CancellationToken>()))
44+
.Returns(Task.CompletedTask)
45+
.Verifiable();
46+
47+
_services.AddScoped(_ => handlerMock.Object);
48+
var provider = _services.BuildServiceProvider();
49+
var mediator = new CodeChavez.M3diator.M3diator(provider);
50+
51+
// Act
52+
await mediator.Handle(request);
53+
54+
// Assert
55+
handlerMock.Verify();
56+
}
57+
58+
[Fact]
59+
public async Task Publish_Notification_CallsAllHandlers()
60+
{
61+
// Arrange
62+
var notification = new TestNotification();
63+
64+
var handlerMock1 = new Mock<INotificationHandler<TestNotification>>();
65+
handlerMock1.Setup(h => h.Handle(notification, It.IsAny<CancellationToken>()))
66+
.Returns(Task.CompletedTask)
67+
.Verifiable();
68+
69+
var handlerMock2 = new Mock<INotificationHandler<TestNotification>>();
70+
handlerMock2.Setup(h => h.Handle(notification, It.IsAny<CancellationToken>()))
71+
.Returns(Task.CompletedTask)
72+
.Verifiable();
73+
74+
_services.AddSingleton(handlerMock1.Object);
75+
_services.AddSingleton(handlerMock2.Object);
76+
var provider = _services.BuildServiceProvider();
77+
var mediator = new CodeChavez.M3diator.M3diator(provider);
78+
79+
// Act
80+
await mediator.Publish(notification);
81+
82+
// Assert
83+
handlerMock1.Verify();
84+
handlerMock2.Verify();
85+
}
86+
87+
// === Mocks ===
88+
89+
public class TestQuery : IRequest<string> { }
90+
91+
public class TestCommand : IRequest { }
92+
93+
public class TestNotification : INotification { }
94+
}

0 commit comments

Comments
 (0)