This commit is contained in:
188
tests/Cqrs.Tests/CommandDispatcherImplTests/Dispatch.cs
Normal file
188
tests/Cqrs.Tests/CommandDispatcherImplTests/Dispatch.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Cqrs.Tests.CommandDispatcherImplTests;
|
||||
|
||||
public class Dispatch
|
||||
{
|
||||
public class TestCommand : IKnownCommand<TestCommandResult> {}
|
||||
public class TestCommandResult {}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public async Task WhenCalled_ShouldExecuteHandler(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
var testCommand = new TestCommand();
|
||||
var testCommandResult = new TestCommandResult();
|
||||
|
||||
var commandHandler = Substitute.For<ICommandHandler<TestCommand, TestCommandResult>>();
|
||||
commandHandler.Handle(testCommand, CancellationToken.None).Returns(testCommandResult);
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(ICommandHandler<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => commandHandler,
|
||||
lifetime
|
||||
),
|
||||
];
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testCommand, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testCommandResult);
|
||||
await commandHandler.Received(1).Handle(testCommand, CancellationToken.None);
|
||||
}
|
||||
|
||||
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly Action<TRequest> _callback;
|
||||
|
||||
public TestOpenBehaviour(Action<TRequest> callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public ValueTask<TResponse> Handle(TRequest request, DispatchFurtherDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
{
|
||||
_callback.Invoke(request);
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenPipelineConfigured_ShouldCallAllBehavioursInOrder()
|
||||
{
|
||||
// Given
|
||||
var testCommand = new TestCommand();
|
||||
var testCommandResult = new TestCommandResult();
|
||||
List<string> calls = [];
|
||||
|
||||
var commandHandler = Substitute.For<ICommandHandler<TestCommand, TestCommandResult>>();
|
||||
commandHandler.Handle(testCommand, CancellationToken.None)
|
||||
.Returns(testCommandResult)
|
||||
.AndDoes(_ => calls.Add("commandHandler"));
|
||||
|
||||
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>();
|
||||
firstBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("firstBehaviour"));
|
||||
|
||||
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>();
|
||||
secondBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("secondBehaviour"));
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(ICommandHandler<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => commandHandler,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => firstBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => secondBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<,>),
|
||||
typeof(TestOpenBehaviour<,>),
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
];
|
||||
serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehaviour"));
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testCommand, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testCommandResult);
|
||||
await firstBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
|
||||
await secondBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
|
||||
await commandHandler.Received(1).Handle(testCommand, CancellationToken.None);
|
||||
|
||||
calls.ShouldBe(["firstBehaviour", "secondBehaviour", "thirdBehaviour", "commandHandler"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenNextIsNotCalled_ShouldStopExecutingPipeline()
|
||||
{
|
||||
// Given
|
||||
var testCommand = new TestCommand();
|
||||
var testCommandResult = new TestCommandResult();
|
||||
var testCommandResultAborted = new TestCommandResult();
|
||||
List<string> calls = [];
|
||||
|
||||
var commandHandler = Substitute.For<ICommandHandler<TestCommand, TestCommandResult>>();
|
||||
commandHandler.Handle(testCommand, CancellationToken.None)
|
||||
.Returns(testCommandResult)
|
||||
.AndDoes(_ => calls.Add("commandHandler"));
|
||||
|
||||
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>();
|
||||
firstBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestCommandResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("firstBehaviour"));
|
||||
|
||||
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestCommand, TestCommandResult>>();
|
||||
secondBehaviour.Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ValueTask.FromResult(testCommandResultAborted))
|
||||
.AndDoes(_ => calls.Add("secondBehaviour"));
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(ICommandHandler<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => commandHandler,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => firstBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestCommand, TestCommandResult>),
|
||||
(IServiceProvider _) => secondBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<,>),
|
||||
typeof(TestOpenBehaviour<,>),
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
];
|
||||
serviceCollection.AddTransient<Action<TestCommand>>(_ => (TestCommand _) => calls.Add("thirdBehaviour"));
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new CommandDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testCommand, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testCommandResultAborted);
|
||||
await firstBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
|
||||
await secondBehaviour.Received(1).Handle(testCommand, Arg.Any<DispatchFurtherDelegate<TestCommandResult>>(), Arg.Any<CancellationToken>());
|
||||
await commandHandler.Received(0).Handle(testCommand, CancellationToken.None);
|
||||
|
||||
calls.ShouldBe(["firstBehaviour", "secondBehaviour"]);
|
||||
}
|
||||
}
|
||||
39
tests/Cqrs.Tests/Cqrs.Tests.csproj
Normal file
39
tests/Cqrs.Tests/Cqrs.Tests.csproj
Normal file
@@ -0,0 +1,39 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<PackageReference Include="NSubstitute" Version="5.3.0" />
|
||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$(TargetFramework) == 'net9.0' Or $(TargetFramework) == 'netstandard2.1'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="$(TargetFramework) == 'net8.0'">
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../src/Just.Cqrs/Just.Cqrs.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
63
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddBehaviour.cs
Normal file
63
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddBehaviour.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cqrs.Tests.CqrsServicesExtensionsTests;
|
||||
|
||||
public class AddBehaviour
|
||||
{
|
||||
public class TestCommand {}
|
||||
public class TestCommandResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class NonGenericTestOpenBehaviour : IDispatchBehaviour<TestCommand, TestCommandResult>
|
||||
{
|
||||
public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public void WhenCalled_ShouldRegisterDispatchBehaviour(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddBehaviour<NonGenericTestOpenBehaviour>(lifetime));
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IDispatchBehaviour<TestCommand, TestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(NonGenericTestOpenBehaviour)
|
||||
&& descriptor.Lifetime == lifetime,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class InvalidTestBehaviour : IDispatchBehaviour
|
||||
{
|
||||
public Type RequestType => throw new NotImplementedException();
|
||||
|
||||
public Type ResponseType => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledWithInvalidType_ShouldThrow()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
|
||||
// Then
|
||||
Should.Throw<InvalidOperationException>(() => services.AddCqrs(opt => opt
|
||||
.AddBehaviour<InvalidTestBehaviour>())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cqrs.Tests.CqrsServicesExtensionsTests;
|
||||
|
||||
public class AddCommandHandler
|
||||
{
|
||||
public class TestCommand {}
|
||||
public class TestCommandResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TestCommandHandler : ICommandHandler<TestCommand, TestCommandResult>
|
||||
{
|
||||
public ValueTask<TestCommandResult> Handle(TestCommand command, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public void WhenCalled_ShouldRegisterCommandHandler(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt.AddCommandHandler<TestCommandHandler>(lifetime));
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandHandler<TestCommand, TestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestCommandHandler)
|
||||
&& descriptor.Lifetime == lifetime,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledMultipleTimes_ShouldRegisterCommandHandlerOnce()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddCommandHandler<TestCommandHandler>()
|
||||
.AddCommandHandler<TestCommandHandler>()
|
||||
.AddCommandHandler<TestCommandHandler>());
|
||||
|
||||
services.AddCqrs(opt => opt
|
||||
.AddCommandHandler<TestCommandHandler>());
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandHandler<TestCommand, TestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestCommandHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
public class SecondTestCommand {}
|
||||
public class SecondTestCommandResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class SecondTestCommandHandler : ICommandHandler<SecondTestCommand, SecondTestCommandResult>
|
||||
{
|
||||
public ValueTask<SecondTestCommandResult> Handle(SecondTestCommand command, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ThirdTestCommand {}
|
||||
public class ThirdTestCommandResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ThirdTestCommandHandler : ICommandHandler<ThirdTestCommand, ThirdTestCommandResult>
|
||||
{
|
||||
public ValueTask<ThirdTestCommandResult> Handle(ThirdTestCommand command, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledMultipleTimes_ShouldRegisterAllCommandHandlers()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddCommandHandler<TestCommandHandler>()
|
||||
.AddCommandHandler<SecondTestCommandHandler>());
|
||||
services.AddCqrs(opt => opt
|
||||
.AddCommandHandler<ThirdTestCommandHandler>());
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandHandler<TestCommand, TestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestCommandHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandHandler<SecondTestCommand, SecondTestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(SecondTestCommandHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandHandler<ThirdTestCommand, ThirdTestCommandResult>)
|
||||
&& descriptor.ImplementationType == typeof(ThirdTestCommandHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cqrs.Tests.CqrsServicesExtensionsTests;
|
||||
|
||||
public class AddOpenBehaviour
|
||||
{
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse>
|
||||
where TRequest: notnull
|
||||
{
|
||||
public ValueTask<TResponse> Handle(TRequest request, DispatchFurtherDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public void WhenCalled_ShouldRegisterOpenDispatchBehaviour(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddOpenBehaviour(typeof(TestOpenBehaviour<,>), lifetime));
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IDispatchBehaviour<,>)
|
||||
&& descriptor.ImplementationType == typeof(TestOpenBehaviour<,>)
|
||||
&& descriptor.Lifetime == lifetime,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class InvalidOpenBehaviour : IDispatchBehaviour
|
||||
{
|
||||
public Type RequestType => throw new NotImplementedException();
|
||||
|
||||
public Type ResponseType => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledWithInvalidType_ShouldThrow()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
var invalidOpenDispatchBehaviourType = typeof(InvalidOpenBehaviour);
|
||||
|
||||
// Then
|
||||
Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt
|
||||
.AddOpenBehaviour(invalidOpenDispatchBehaviourType))
|
||||
);
|
||||
}
|
||||
|
||||
public class TestCommand {}
|
||||
public class TestCommandResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class NonGenericTestOpenBehaviour : IDispatchBehaviour<TestCommand, TestCommandResult>
|
||||
{
|
||||
public ValueTask<TestCommandResult> Handle(TestCommand request, DispatchFurtherDelegate<TestCommandResult> next, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledWithNonGenericType_ShouldThrow()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
var nonGenericOpenDispatchBehaviourType = typeof(NonGenericTestOpenBehaviour);
|
||||
|
||||
// Then
|
||||
Should.Throw<ArgumentException>(() => services.AddCqrs(opt => opt
|
||||
.AddOpenBehaviour(nonGenericOpenDispatchBehaviourType))
|
||||
);
|
||||
}
|
||||
}
|
||||
124
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddQueryHandler.cs
Normal file
124
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddQueryHandler.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cqrs.Tests.CqrsServicesExtensionsTests;
|
||||
|
||||
public class AddQueryHandler
|
||||
{
|
||||
public class TestQuery {}
|
||||
public class TestQueryResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class TestQueryHandler : IQueryHandler<TestQuery, TestQueryResult>
|
||||
{
|
||||
public ValueTask<TestQueryResult> Handle(TestQuery Query, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public void WhenCalled_ShouldRegisterQueryHandler(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt.AddQueryHandler<TestQueryHandler>(lifetime));
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryHandler<TestQuery, TestQueryResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestQueryHandler)
|
||||
&& descriptor.Lifetime == lifetime,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledMultipleTimes_ShouldRegisterQueryHandlerOnce()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddQueryHandler<TestQueryHandler>()
|
||||
.AddQueryHandler<TestQueryHandler>()
|
||||
.AddQueryHandler<TestQueryHandler>());
|
||||
|
||||
services.AddCqrs(opt => opt
|
||||
.AddQueryHandler<TestQueryHandler>());
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryHandler<TestQuery, TestQueryResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestQueryHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
public class SecondTestQuery {}
|
||||
public class SecondTestQueryResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class SecondTestQueryHandler : IQueryHandler<SecondTestQuery, SecondTestQueryResult>
|
||||
{
|
||||
public ValueTask<SecondTestQueryResult> Handle(SecondTestQuery Query, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public class ThirdTestQuery {}
|
||||
public class ThirdTestQueryResult {}
|
||||
[ExcludeFromCodeCoverage]
|
||||
public class ThirdTestQueryHandler : IQueryHandler<ThirdTestQuery, ThirdTestQueryResult>
|
||||
{
|
||||
public ValueTask<ThirdTestQueryResult> Handle(ThirdTestQuery Query, CancellationToken cancellation)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledMultipleTimes_ShouldRegisterAllQueryHandlers()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs(opt => opt
|
||||
.AddQueryHandler<TestQueryHandler>()
|
||||
.AddQueryHandler<SecondTestQueryHandler>());
|
||||
services.AddCqrs(opt => opt
|
||||
.AddQueryHandler<ThirdTestQueryHandler>());
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryHandler<TestQuery, TestQueryResult>)
|
||||
&& descriptor.ImplementationType == typeof(TestQueryHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryHandler<SecondTestQuery, SecondTestQueryResult>)
|
||||
&& descriptor.ImplementationType == typeof(SecondTestQueryHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryHandler<ThirdTestQuery, ThirdTestQueryResult>)
|
||||
&& descriptor.ImplementationType == typeof(ThirdTestQueryHandler)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
}
|
||||
63
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddSqrs.cs
Normal file
63
tests/Cqrs.Tests/CqrsServicesExtensionsTests/AddSqrs.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Cqrs.Tests.CqrsServicesExtensionsTests;
|
||||
|
||||
public class AddCqrs
|
||||
{
|
||||
[Fact]
|
||||
public void WhenCalled_ShouldRegisterDispatcherClasses()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs();
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandDispatcher)
|
||||
&& descriptor.ImplementationType == typeof(CommandDispatcherImpl)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryDispatcher)
|
||||
&& descriptor.ImplementationType == typeof(QueryDispatcherImpl)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenCalledMultipleTimes_ShouldRegisterDispatcherClassesOnce()
|
||||
{
|
||||
// Given
|
||||
ServiceCollection services = new();
|
||||
|
||||
// When
|
||||
services.AddCqrs();
|
||||
services.AddCqrs();
|
||||
services.AddCqrs();
|
||||
services.AddCqrs();
|
||||
|
||||
// Then
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(ICommandDispatcher)
|
||||
&& descriptor.ImplementationType == typeof(CommandDispatcherImpl)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
|
||||
services.ShouldContain(
|
||||
elementPredicate: descriptor =>
|
||||
descriptor.ServiceType == typeof(IQueryDispatcher)
|
||||
&& descriptor.ImplementationType == typeof(QueryDispatcherImpl)
|
||||
&& descriptor.Lifetime == ServiceLifetime.Transient,
|
||||
expectedCount: 1
|
||||
);
|
||||
}
|
||||
}
|
||||
188
tests/Cqrs.Tests/QueryDispatcherImplTests/Dispatch.cs
Normal file
188
tests/Cqrs.Tests/QueryDispatcherImplTests/Dispatch.cs
Normal file
@@ -0,0 +1,188 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Cqrs.Tests.QueryDispatcherImplTests;
|
||||
|
||||
public class Dispatch
|
||||
{
|
||||
public class TestQuery : IKnownQuery<TestQueryResult> {}
|
||||
public class TestQueryResult {}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Transient)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
public async Task WhenCalled_ShouldExecuteHandler(ServiceLifetime lifetime)
|
||||
{
|
||||
// Given
|
||||
var testQuery = new TestQuery();
|
||||
var testQueryResult = new TestQueryResult();
|
||||
|
||||
var queryHandler = Substitute.For<IQueryHandler<TestQuery, TestQueryResult>>();
|
||||
queryHandler.Handle(testQuery, CancellationToken.None).Returns(testQueryResult);
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(IQueryHandler<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => queryHandler,
|
||||
lifetime
|
||||
),
|
||||
];
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testQuery, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testQueryResult);
|
||||
await queryHandler.Received(1).Handle(testQuery, CancellationToken.None);
|
||||
}
|
||||
|
||||
public class TestOpenBehaviour<TRequest, TResponse> : IDispatchBehaviour<TRequest, TResponse>
|
||||
where TRequest : notnull
|
||||
{
|
||||
private readonly Action<TRequest> _callback;
|
||||
|
||||
public TestOpenBehaviour(Action<TRequest> callback)
|
||||
{
|
||||
_callback = callback;
|
||||
}
|
||||
|
||||
public ValueTask<TResponse> Handle(TRequest request, DispatchFurtherDelegate<TResponse> next, CancellationToken cancellationToken)
|
||||
{
|
||||
_callback.Invoke(request);
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenPipelineConfigured_ShouldCallAllBehavioursInOrder()
|
||||
{
|
||||
// Given
|
||||
var testQuery = new TestQuery();
|
||||
var testQueryResult = new TestQueryResult();
|
||||
List<string> calls = [];
|
||||
|
||||
var queryHandler = Substitute.For<IQueryHandler<TestQuery, TestQueryResult>>();
|
||||
queryHandler.Handle(testQuery, CancellationToken.None)
|
||||
.Returns(testQueryResult)
|
||||
.AndDoes(_ => calls.Add("queryHandler"));
|
||||
|
||||
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>();
|
||||
firstBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("firstBehaviour"));
|
||||
|
||||
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>();
|
||||
secondBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("secondBehaviour"));
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(IQueryHandler<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => queryHandler,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => firstBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => secondBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<,>),
|
||||
typeof(TestOpenBehaviour<,>),
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
];
|
||||
serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehaviour"));
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testQuery, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testQueryResult);
|
||||
await firstBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
|
||||
await secondBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
|
||||
await queryHandler.Received(1).Handle(testQuery, CancellationToken.None);
|
||||
|
||||
calls.ShouldBe(["firstBehaviour", "secondBehaviour", "thirdBehaviour", "queryHandler"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WhenNextIsNotCalled_ShouldStopExecutingPipeline()
|
||||
{
|
||||
// Given
|
||||
var testQuery = new TestQuery();
|
||||
var testQueryResult = new TestQueryResult();
|
||||
var testQueryResultAborted = new TestQueryResult();
|
||||
List<string> calls = [];
|
||||
|
||||
var queryHandler = Substitute.For<IQueryHandler<TestQuery, TestQueryResult>>();
|
||||
queryHandler.Handle(testQuery, CancellationToken.None)
|
||||
.Returns(testQueryResult)
|
||||
.AndDoes(_ => calls.Add("queryHandler"));
|
||||
|
||||
var firstBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>();
|
||||
firstBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ((DispatchFurtherDelegate<TestQueryResult>)args[1]).Invoke())
|
||||
.AndDoes(_ => calls.Add("firstBehaviour"));
|
||||
|
||||
var secondBehaviour = Substitute.For<IDispatchBehaviour<TestQuery, TestQueryResult>>();
|
||||
secondBehaviour.Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>())
|
||||
.Returns(args => ValueTask.FromResult(testQueryResultAborted))
|
||||
.AndDoes(_ => calls.Add("secondBehaviour"));
|
||||
|
||||
ServiceCollection serviceCollection =
|
||||
[
|
||||
new ServiceDescriptor(
|
||||
typeof(IQueryHandler<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => queryHandler,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => firstBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<TestQuery, TestQueryResult>),
|
||||
(IServiceProvider _) => secondBehaviour,
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
new ServiceDescriptor(
|
||||
typeof(IDispatchBehaviour<,>),
|
||||
typeof(TestOpenBehaviour<,>),
|
||||
ServiceLifetime.Transient
|
||||
),
|
||||
];
|
||||
serviceCollection.AddTransient<Action<TestQuery>>(_ => (TestQuery _) => calls.Add("thirdBehaviour"));
|
||||
var services = serviceCollection.BuildServiceProvider();
|
||||
|
||||
var sut = new QueryDispatcherImpl(services, new ConcurrentMethodsCache());
|
||||
|
||||
// When
|
||||
var result = await sut.Dispatch(testQuery, CancellationToken.None);
|
||||
|
||||
// Then
|
||||
result.ShouldBeSameAs(testQueryResultAborted);
|
||||
await firstBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
|
||||
await secondBehaviour.Received(1).Handle(testQuery, Arg.Any<DispatchFurtherDelegate<TestQueryResult>>(), Arg.Any<CancellationToken>());
|
||||
await queryHandler.Received(0).Handle(testQuery, CancellationToken.None);
|
||||
|
||||
calls.ShouldBe(["firstBehaviour", "secondBehaviour"]);
|
||||
}
|
||||
}
|
||||
3
tests/Cqrs.Tests/usings.cs
Normal file
3
tests/Cqrs.Tests/usings.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
global using Shouldly;
|
||||
global using Just.Cqrs;
|
||||
global using Just.Cqrs.Internal;
|
||||
Reference in New Issue
Block a user