Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"PulsarTests",
"RabbitmqTests",
"Restore",
"ShimsTests",
"SqliteTests",
"Test",
"TestExtensions",
Expand Down
15 changes: 14 additions & 1 deletion build/build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ class Build : NukeBuild
.SetFramework(Framework));
});

Target ShimsTests => _ => _
.DependsOn(Compile)
.ProceedAfterFailure()
.Executes(() =>
{
DotNetTest(c => c
.SetProjectFile(Solution.Testing.ShimsTests)
.SetConfiguration(Configuration)
.EnableNoBuild()
.EnableNoRestore()
.SetFramework(Framework));
});

Target TestExtensions => _ => _
.DependsOn(FluentValidationTests, DataAnnotationsValidationTests, MemoryPackTests, MessagePackTests);

Expand Down Expand Up @@ -566,4 +579,4 @@ private void removeProject(string repository, string projectName)



}
}
39 changes: 39 additions & 0 deletions src/Testing/ShimsTests/MediatR/MediatRHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using Wolverine.Shims.MediatR;
using Wolverine.Attributes;

namespace Wolverine.Shims.Tests.MediatR;

public class RequestWithResponseHandler : IRequestHandler<RequestWithResponse, Response>
{
public Task<Response> Handle(RequestWithResponse request, CancellationToken cancellationToken)
{
return Task.FromResult(new Response($"passed: {request.Data}", "MediatR"));
}
}

public class RequestWithoutResponseHandler : IRequestHandler<RequestWithoutResponse>
{
public Task Handle(RequestWithoutResponse request, CancellationToken cancellationToken)
{
// Just process and return
return Task.CompletedTask;
}
}


public class RequestCascadeHandler : IRequestHandler<RequestCascade, CascadingMessage>
{
public async Task<CascadingMessage> Handle(RequestCascade request, CancellationToken cancellationToken)
{
return new CascadingMessage(request.Data);
}
}

public class RequestAdditionHandler(IAdditionService service) : IRequestHandler<RequestAdditionFromService, int>
{
public async Task<int> Handle(RequestAdditionFromService request, CancellationToken cancellationToken)
{
return service.Process(request.number);
}

}
29 changes: 29 additions & 0 deletions src/Testing/ShimsTests/MediatR/Messages.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Wolverine.Shims.MediatR;

namespace Wolverine.Shims.Tests.MediatR;

public record RequestWithResponse(string Data) : IRequest<Response>;
public record Response(string Data, string ProcessedBy);

public record RequestWithoutResponse(string Data) : IRequest;


public record RequestCascade(string Data) : IRequest<CascadingMessage>;
public record CascadingMessage(string Data);

public record RequestAdditionFromService(int number) : IRequest<int>;


public interface IAdditionService
{
int Process(int number);
}

public class AdditionService : IAdditionService
{
public int Process(int number)
{
return number + 1;
}

}
14 changes: 14 additions & 0 deletions src/Testing/ShimsTests/MediatR/WolverineHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Wolverine.Attributes;

namespace Wolverine.Shims.Tests.MediatR;

public static class CascadingMessageHandler
{
public static string? ReceivedData { get; set; }

[WolverineHandler]
public static void Handle(CascadingMessage message)
{
ReceivedData = message.Data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.Extensions.Hosting;
using Shouldly;
using Xunit;
using Alba;

namespace Wolverine.Shims.Tests.MediatR;

public class mediatr_shim_cascading_message_tests
{
private IHostBuilder _builder;
public mediatr_shim_cascading_message_tests()
{
_builder = Host.CreateDefaultBuilder();
_builder.UseWolverine(opts =>
{
opts.Discovery.IncludeAssembly(typeof(mediatr_shim_cascading_message_tests).Assembly);
opts.UseMediatRHandlers();
});
}

[Fact]
public async Task mediatr_handler_can_return_cascading_message()
{
await using var host = await AlbaHost.For(_builder);

// Reset static state
CascadingMessageHandler.ReceivedData = null;

// Invoke the message and wait for cascading messages to be processed
await host.InvokeAsync(new CascadingMessage("cascade-test"));

// Verify the cascading message was handled
CascadingMessageHandler.ReceivedData.ShouldBe("cascade-test");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;
using Xunit;
using Alba;

namespace Wolverine.Shims.Tests.MediatR;

public class mediatr_shim_handler_integration_tests
{
private IHostBuilder _builder;
public mediatr_shim_handler_integration_tests()
{
_builder = Host.CreateDefaultBuilder();
_builder.UseWolverine(opts =>
{
opts.Discovery.IncludeAssembly(typeof(mediatr_shim_cascading_message_tests).Assembly);
opts.Services.AddScoped<IAdditionService, AdditionService>();
opts.UseMediatRHandlers();
});
}

[Fact]
public async Task invoke_mediatr_handler_with_response()
{
await using var host = await AlbaHost.For(_builder);

var response = await host.MessageBus().InvokeAsync<Response>(
new RequestWithResponse("test"));

response.ShouldNotBeNull();
response.Data.ShouldBe("passed: test");
response.ProcessedBy.ShouldBe("MediatR");
}

[Fact]
public async Task invoke_mediatr_handler_without_response()
{
await using var host = await AlbaHost.For(_builder);

// Should not throw
await host.InvokeAsync(new RequestWithoutResponse("test"));
}

[Fact]
public async Task mediatr_handler_receives_dependencies_from_di()
{
await using var host = await AlbaHost.For(_builder);

var response = await host.MessageBus().InvokeAsync<int>(
new RequestAdditionFromService(1));

response.ShouldBe(2);
}
}
43 changes: 43 additions & 0 deletions src/Testing/ShimsTests/MediatR/mediatr_shim_response_tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.Extensions.Hosting;
using Shouldly;
using Xunit;
using Alba;

namespace Wolverine.Shims.Tests.MediatR;

public class mediatr_shim_response_tests
{
private IHostBuilder _builder;
public mediatr_shim_response_tests()
{
_builder = Host.CreateDefaultBuilder();
_builder.UseWolverine(opts =>
{
opts.Discovery.IncludeAssembly(typeof(mediatr_shim_cascading_message_tests).Assembly);
opts.UseMediatRHandlers();
});
}

[Fact]
public async Task response_is_returned_from_invoke_async()
{
await using var host = await AlbaHost.For(_builder);

var response = await host.MessageBus().InvokeAsync<Response>(
new RequestWithResponse("response-test"));

response.ShouldNotBeNull();
response.Data.ShouldBe("passed: response-test");
}

[Fact]
public async Task response_type_is_correct()
{
await using var host = await AlbaHost.For(_builder);

var response = await host.MessageBus().InvokeAsync<Response>(
new RequestWithResponse("type-test"));

response.ShouldBeOfType<Response>();
}
}
25 changes: 25 additions & 0 deletions src/Testing/ShimsTests/ShimsTests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
<TargetFrameworks>net9.0</TargetFrameworks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Alba" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Shouldly" />
<PackageReference Include="GitHubActionsTestLogger" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\Wolverine\Wolverine.csproj"/>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup>

</Project>
21 changes: 21 additions & 0 deletions src/Wolverine/Shims/MediatR/IRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Threading;
using System.Threading.Tasks;

namespace Wolverine.Shims.MediatR;

/// <summary>
/// Marker interface for requests that return a response.
/// This is a shim interface compatible with MediatR's IRequest&lt;T&gt;.
/// </summary>
/// <typeparam name="T">The response type</typeparam>
public interface IRequest<out T>
{
}

/// <summary>
/// Marker interface for requests that do not return a response.
/// This is a shim interface compatible with MediatR's IRequest.
/// </summary>
public interface IRequest
{
}
39 changes: 39 additions & 0 deletions src/Wolverine/Shims/MediatR/IRequestHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System.Threading;
using System.Threading.Tasks;

namespace Wolverine.Shims.MediatR;

/// <summary>
/// Handler for requests that return a response.
/// This is a shim interface compatible with MediatR's IRequestHandler&lt;TRequest, TResponse&gt;.
/// Handlers implementing this interface will be automatically discovered by Wolverine.
/// </summary>
/// <typeparam name="TRequest">The request type</typeparam>
/// <typeparam name="TResponse">The response type</typeparam>
public interface IRequestHandler<in TRequest, TResponse> where TRequest : IRequest<TResponse>
{
/// <summary>
/// Handles a request and returns a response
/// </summary>
/// <param name="request">The request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Response from the request</returns>
Task<TResponse> Handle(TRequest request, CancellationToken cancellationToken);
}

/// <summary>
/// Handler for requests that do not return a response.
/// This is a shim interface compatible with MediatR's IRequestHandler&lt;TRequest&gt;.
/// Handlers implementing this interface will be automatically discovered by Wolverine.
/// </summary>
/// <typeparam name="TRequest">The request type</typeparam>
public interface IRequestHandler<in TRequest> where TRequest : IRequest
{
/// <summary>
/// Handles a request without returning a response
/// </summary>
/// <param name="request">The request</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>A task representing the completion of the request</returns>
Task Handle(TRequest request, CancellationToken cancellationToken);
}
25 changes: 25 additions & 0 deletions src/Wolverine/Shims/MediatR/WolverineOptionsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Wolverine.Shims.MediatR;

namespace Wolverine.Shims;

/// <summary>
/// Extension methods for using MediatR-style handlers in Wolverine
/// </summary>
public static class WolverineOptionsExtensions
{
/// <summary>
/// Enables discovery of handlers implementing Wolverine's MediatR shim interfaces
/// (IRequestHandler&lt;TRequest, TResponse&gt; and IRequestHandler&lt;TRequest&gt;).
/// This allows you to use MediatR-style handler patterns without depending on the MediatR library.
/// </summary>
public static WolverineOptions UseMediatRHandlers(this WolverineOptions options)
{
options.Discovery.CustomizeHandlerDiscovery(query =>
{
query.Includes.Implements(typeof(IRequestHandler<>));
query.Includes.Implements(typeof(IRequestHandler<,>));
});

return options;
}
}
Loading
Loading