diff --git a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs index 4be106bf..5e10b199 100644 --- a/src/MySystem.AcceptanceTests/When_sending_AMessage.cs +++ b/src/MySystem.AcceptanceTests/When_sending_AMessage.cs @@ -17,7 +17,7 @@ public class When_sending_AMessage public async Task AReplyMessage_is_received_and_ASaga_is_started() { var theExpectedSagaId = Guid.NewGuid(); - var context = await Scenario.Define() + var context = await Scenario.Define() .WithEndpoint(g => g.When(b => b.Send(new AMessage() { ThisWillBeTheSagaId = theExpectedSagaId }))) .WithEndpoint() .Done(c => diff --git a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs index 7ee23936..55a9f4a1 100644 --- a/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs +++ b/src/MySystem.AcceptanceTests/When_sending_CompleteASaga.cs @@ -16,7 +16,7 @@ public class When_sending_CompleteASaga public async Task ASaga_is_completed() { var theExpectedSagaId = Guid.NewGuid(); - var context = await Scenario.Define() + var context = await Scenario.Define() .WithEndpoint(g => { g.When(session => session.Send("MyService", new StartASaga() { SomeId = theExpectedSagaId })); diff --git a/src/NServiceBus.IntegrationTesting.Tests/NServiceBus.IntegrationTesting.Tests.csproj b/src/NServiceBus.IntegrationTesting.Tests/NServiceBus.IntegrationTesting.Tests.csproj new file mode 100644 index 00000000..84631260 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Tests/NServiceBus.IntegrationTesting.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/src/NServiceBus.IntegrationTesting.Tests/Publish_Operation_Interceptor.cs b/src/NServiceBus.IntegrationTesting.Tests/Publish_Operation_Interceptor.cs new file mode 100644 index 00000000..30e188ce --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Tests/Publish_Operation_Interceptor.cs @@ -0,0 +1,31 @@ +using MyMessages.Messages; +using NServiceBus.Pipeline; +using NServiceBus.Testing; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.Tests +{ + public class Publish_Operation_Interceptor + { + [Test] + public async Task Should_Capture_Publish_Message_Operation() + { + var scenarioContext = new IntegrationScenarioContext(); + var context = new TestableOutgoingPublishContext + { + Message = new OutgoingLogicalMessage(typeof(AMessage), new AMessage()) + }; + + var sut = new InterceptPublishOperations("fake-endpoint", scenarioContext); + await sut.Invoke(context, () => Task.CompletedTask).ConfigureAwait(false); + + var operation = scenarioContext.OutgoingMessageOperations.SingleOrDefault() as PublishOperation; + + Assert.AreEqual(1, scenarioContext.OutgoingMessageOperations.Count()); + Assert.IsNotNull(operation); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.Tests/Reply_Operation_Interceptor.cs b/src/NServiceBus.IntegrationTesting.Tests/Reply_Operation_Interceptor.cs new file mode 100644 index 00000000..90e3bb4c --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Tests/Reply_Operation_Interceptor.cs @@ -0,0 +1,31 @@ +using MyMessages.Messages; +using NServiceBus.Pipeline; +using NServiceBus.Testing; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.Tests +{ + public class Reply_Operation_Interceptor + { + [Test] + public async Task Should_Capture_Reply_Message_Operation() + { + var scenarioContext = new IntegrationScenarioContext(); + var context = new TestableOutgoingReplyContext + { + Message = new OutgoingLogicalMessage(typeof(AMessage), new AMessage()) + }; + + var sut = new InterceptReplyOperations("fake-endpoint", scenarioContext); + await sut.Invoke(context, () => Task.CompletedTask).ConfigureAwait(false); + + var operation = scenarioContext.OutgoingMessageOperations.SingleOrDefault() as ReplyOperation; + + Assert.AreEqual(1, scenarioContext.OutgoingMessageOperations.Count()); + Assert.IsNotNull(operation); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.Tests/Send_Operation_Interceptor.cs b/src/NServiceBus.IntegrationTesting.Tests/Send_Operation_Interceptor.cs new file mode 100644 index 00000000..487959f5 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting.Tests/Send_Operation_Interceptor.cs @@ -0,0 +1,57 @@ +using MyMessages.Messages; +using NServiceBus.Pipeline; +using NServiceBus.Testing; +using NUnit.Framework; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting.Tests +{ + public class Send_Operation_Interceptor + { + [Test] + public async Task Should_Capture_Sent_Message_Operation() + { + var scenarioContext = new IntegrationScenarioContext(); + var context = new TestableOutgoingSendContext + { + Message = new OutgoingLogicalMessage(typeof(AMessage), new AMessage()) + }; + + var sut = new InterceptSendOperations("fake-endpoint", scenarioContext); + await sut.Invoke(context, () => Task.CompletedTask).ConfigureAwait(false); + + var sendOperation = scenarioContext.OutgoingMessageOperations.SingleOrDefault() as SendOperation; + + Assert.AreEqual(1, scenarioContext.OutgoingMessageOperations.Count()); + Assert.IsNotNull(sendOperation); + } + + [Test] + public async Task Should_Capture_TimeoutRequest_Operation() + { + var expectedSagaId = "a-saga-id"; + var expectedSagaType = "a-saga-type"; + + var scenarioContext = new IntegrationScenarioContext(); + var context = new TestableOutgoingSendContext + { + Message = new OutgoingLogicalMessage(typeof(AMessage), new AMessage()) + }; + context.Headers.Add(Headers.SagaId, expectedSagaId); + context.Headers.Add(Headers.SagaType, expectedSagaType); + context.Headers.Add(Headers.IsSagaTimeoutMessage, bool.TrueString); + + var sut = new InterceptSendOperations("fake-endpoint", scenarioContext); ; + await sut.Invoke(context, () => Task.CompletedTask).ConfigureAwait(false); + + var requestTimeoutOperation = scenarioContext.OutgoingMessageOperations.SingleOrDefault() as RequestTimeoutOperation; + + Assert.AreEqual(1, scenarioContext.OutgoingMessageOperations.Count()); + Assert.IsNotNull(requestTimeoutOperation); + Assert.AreEqual(expectedSagaId, requestTimeoutOperation.SagaId); + Assert.AreEqual(expectedSagaType, requestTimeoutOperation.SagaTypeAssemblyQualifiedName); + } + } +} diff --git a/src/NServiceBus.IntegrationTesting.sln b/src/NServiceBus.IntegrationTesting.sln index f304112b..152218b9 100644 --- a/src/NServiceBus.IntegrationTesting.sln +++ b/src/NServiceBus.IntegrationTesting.sln @@ -13,7 +13,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MySystem.AcceptanceTests", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.IntegrationTesting", "NServiceBus.IntegrationTesting\NServiceBus.IntegrationTesting.csproj", "{A1DF8428-D478-4FAA-9237-AA36D4C83732}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.AssemblyScanner.Extensions", "NServiceBus.AssemblyScanner.Extensions\NServiceBus.AssemblyScanner.Extensions.csproj", "{DA921CE7-6B53-4196-8567-B0197E773043}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NServiceBus.AssemblyScanner.Extensions", "NServiceBus.AssemblyScanner.Extensions\NServiceBus.AssemblyScanner.Extensions.csproj", "{DA921CE7-6B53-4196-8567-B0197E773043}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NServiceBus.IntegrationTesting.Tests", "NServiceBus.IntegrationTesting.Tests\NServiceBus.IntegrationTesting.Tests.csproj", "{18A4490B-641E-471C-9A53-02C4AAB69551}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -45,6 +47,10 @@ Global {DA921CE7-6B53-4196-8567-B0197E773043}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA921CE7-6B53-4196-8567-B0197E773043}.Release|Any CPU.ActiveCfg = Release|Any CPU {DA921CE7-6B53-4196-8567-B0197E773043}.Release|Any CPU.Build.0 = Release|Any CPU + {18A4490B-641E-471C-9A53-02C4AAB69551}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {18A4490B-641E-471C-9A53-02C4AAB69551}.Debug|Any CPU.Build.0 = Debug|Any CPU + {18A4490B-641E-471C-9A53-02C4AAB69551}.Release|Any CPU.ActiveCfg = Release|Any CPU + {18A4490B-641E-471C-9A53-02C4AAB69551}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/NServiceBus.IntegrationTesting/EndpointTemplate.cs b/src/NServiceBus.IntegrationTesting/EndpointTemplate.cs index 1ef41992..6402ed6b 100644 --- a/src/NServiceBus.IntegrationTesting/EndpointTemplate.cs +++ b/src/NServiceBus.IntegrationTesting/EndpointTemplate.cs @@ -16,7 +16,10 @@ public async Task GetConfiguration(RunDescriptor runDescr configurationBuilderCustomization(configuration); - configuration.Pipeline.Register(new InterceptInvokedHandlers(endpointCustomizationConfiguration.EndpointName), "Intercept invoked Message Handlers"); + configuration.Pipeline.Register(new InterceptInvokedHandlers(endpointCustomizationConfiguration.EndpointName, (IntegrationScenarioContext)runDescriptor.ScenarioContext), "Intercept invoked Message Handlers and Sagas"); + configuration.Pipeline.Register(new InterceptSendOperations(endpointCustomizationConfiguration.EndpointName, (IntegrationScenarioContext)runDescriptor.ScenarioContext), "Intercept send operations"); + configuration.Pipeline.Register(new InterceptPublishOperations(endpointCustomizationConfiguration.EndpointName, (IntegrationScenarioContext)runDescriptor.ScenarioContext), "Intercept publish operations"); + configuration.Pipeline.Register(new InterceptReplyOperations(endpointCustomizationConfiguration.EndpointName, (IntegrationScenarioContext)runDescriptor.ScenarioContext), "Intercept reply operations"); return configuration; } diff --git a/src/NServiceBus.IntegrationTesting/IntegrationContext.cs b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs similarity index 75% rename from src/NServiceBus.IntegrationTesting/IntegrationContext.cs rename to src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs index 1f6ad968..4fb58d40 100644 --- a/src/NServiceBus.IntegrationTesting/IntegrationContext.cs +++ b/src/NServiceBus.IntegrationTesting/IntegrationScenarioContext.cs @@ -6,13 +6,15 @@ namespace NServiceBus.IntegrationTesting { - public class IntegrationContext : ScenarioContext + public class IntegrationScenarioContext : ScenarioContext { readonly ConcurrentBag invokedHandlers = new ConcurrentBag(); readonly ConcurrentBag invokedSagas = new ConcurrentBag(); + readonly ConcurrentBag outgoingMessageOperations = new ConcurrentBag(); public IEnumerable InvokedHandlers { get { return invokedHandlers; } } public IEnumerable InvokedSagas { get { return invokedSagas; } } + public IEnumerable OutgoingMessageOperations { get { return outgoingMessageOperations; } } internal HandlerInvocation CaptureInvokedHandler(HandlerInvocation invocation) { @@ -21,27 +23,16 @@ internal HandlerInvocation CaptureInvokedHandler(HandlerInvocation invocation) return invocation; } - internal SagaInvocation CaptureInvokedSaga(SagaInvocation invocation) - { - invokedSagas.Add(invocation); - - return invocation; - } - - static PropertyInfo GetScenarioContextCurrentProperty() + internal void AddOutogingOperation(OutgoingMessageOperation outgoingMessageOperation) { - return typeof(ScenarioContext).GetProperty("Current", BindingFlags.Static | BindingFlags.NonPublic); + outgoingMessageOperations.Add(outgoingMessageOperation); } - public static IntegrationContext CurrentContext + internal SagaInvocation CaptureInvokedSaga(SagaInvocation invocation) { - get - { - var pi = GetScenarioContextCurrentProperty(); - var current = (IntegrationContext)pi.GetMethod.Invoke(null, null); + invokedSagas.Add(invocation); - return current; - } + return invocation; } public bool HandlerWasInvoked() diff --git a/src/NServiceBus.IntegrationTesting/InterceptInvokedHandlers.cs b/src/NServiceBus.IntegrationTesting/InterceptInvokedHandlers.cs index 85cae427..5b9077d2 100644 --- a/src/NServiceBus.IntegrationTesting/InterceptInvokedHandlers.cs +++ b/src/NServiceBus.IntegrationTesting/InterceptInvokedHandlers.cs @@ -8,10 +8,12 @@ namespace NServiceBus.IntegrationTesting class InterceptInvokedHandlers : Behavior { readonly string endpointName; + readonly IntegrationScenarioContext integrationContext; - public InterceptInvokedHandlers(string endpointName) + public InterceptInvokedHandlers(string endpointName, IntegrationScenarioContext integrationContext) { this.endpointName = endpointName; + this.integrationContext = integrationContext; } public override async Task Invoke(IInvokeHandlerContext context, Func next) @@ -23,7 +25,7 @@ public override async Task Invoke(IInvokeHandlerContext context, Func next if (context.Extensions.TryGet(out ActiveSagaInstance saga)) { - invocation = IntegrationContext.CurrentContext.CaptureInvokedSaga(new SagaInvocation() + invocation = integrationContext.CaptureInvokedSaga(new SagaInvocation() { NotFound = saga.NotFound, SagaType = saga.NotFound ? null : saga.Instance.GetType(), @@ -34,7 +36,7 @@ public override async Task Invoke(IInvokeHandlerContext context, Func next } else { - invocation = IntegrationContext.CurrentContext.CaptureInvokedHandler(new HandlerInvocation() + invocation = integrationContext.CaptureInvokedHandler(new HandlerInvocation() { HandlerType = context.MessageHandler.HandlerType }); diff --git a/src/NServiceBus.IntegrationTesting/InterceptPublishOperations.cs b/src/NServiceBus.IntegrationTesting/InterceptPublishOperations.cs new file mode 100644 index 00000000..ee8abc0c --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/InterceptPublishOperations.cs @@ -0,0 +1,41 @@ +using NServiceBus.Pipeline; +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + class InterceptPublishOperations : Behavior + { + readonly string endpointName; + readonly IntegrationScenarioContext integrationContext; + + public InterceptPublishOperations(string endpointName, IntegrationScenarioContext integrationContext) + { + this.endpointName = endpointName; + this.integrationContext = integrationContext; + } + + public override async Task Invoke(IOutgoingPublishContext context, Func next) + { + var sendOperation = new PublishOperation + { + SenderEndpoint = endpointName, + MessageId = context.MessageId, + MessageType = context.Message.MessageType, + MessageInstance = context.Message.Instance, + MessageHeaders = context.Headers + }; + + integrationContext.AddOutogingOperation(sendOperation); + try + { + await next(); + } + catch(Exception sendError) + { + sendOperation.OperationError = sendError; + throw; + } + } + } +} diff --git a/src/NServiceBus.IntegrationTesting/InterceptReplyOperations.cs b/src/NServiceBus.IntegrationTesting/InterceptReplyOperations.cs new file mode 100644 index 00000000..84a5fdd3 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/InterceptReplyOperations.cs @@ -0,0 +1,41 @@ +using NServiceBus.Pipeline; +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + class InterceptReplyOperations : Behavior + { + readonly string endpointName; + readonly IntegrationScenarioContext integrationContext; + + public InterceptReplyOperations(string endpointName, IntegrationScenarioContext integrationContext) + { + this.endpointName = endpointName; + this.integrationContext = integrationContext; + } + + public override async Task Invoke(IOutgoingReplyContext context, Func next) + { + var sendOperation = new ReplyOperation + { + SenderEndpoint = endpointName, + MessageId = context.MessageId, + MessageType = context.Message.MessageType, + MessageInstance = context.Message.Instance, + MessageHeaders = context.Headers + }; + + integrationContext.AddOutogingOperation(sendOperation); + try + { + await next(); + } + catch(Exception sendError) + { + sendOperation.OperationError = sendError; + throw; + } + } + } +} diff --git a/src/NServiceBus.IntegrationTesting/InterceptSendOperations.cs b/src/NServiceBus.IntegrationTesting/InterceptSendOperations.cs new file mode 100644 index 00000000..b047074e --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/InterceptSendOperations.cs @@ -0,0 +1,52 @@ +using NServiceBus.Pipeline; +using System; +using System.Threading.Tasks; + +namespace NServiceBus.IntegrationTesting +{ + class InterceptSendOperations : Behavior + { + readonly string endpointName; + readonly IntegrationScenarioContext integrationContext; + + public InterceptSendOperations(string endpointName, IntegrationScenarioContext integrationContext) + { + this.endpointName = endpointName; + this.integrationContext = integrationContext; + } + + public override async Task Invoke(IOutgoingSendContext context, Func next) + { + OutgoingMessageOperation outgoingOperation; + if (context.Headers.ContainsKey(Headers.IsSagaTimeoutMessage) && context.Headers[Headers.IsSagaTimeoutMessage] == bool.TrueString) + { + outgoingOperation = new RequestTimeoutOperation() + { + SagaId = context.Headers[Headers.SagaId], + SagaTypeAssemblyQualifiedName = context.Headers[Headers.SagaType] + }; + } + else + { + outgoingOperation = new SendOperation(); + } + + outgoingOperation.SenderEndpoint = endpointName; + outgoingOperation.MessageId = context.MessageId; + outgoingOperation.MessageType = context.Message.MessageType; + outgoingOperation.MessageInstance = context.Message.Instance; + outgoingOperation.MessageHeaders = context.Headers; + + integrationContext.AddOutogingOperation(outgoingOperation); + try + { + await next(); + } + catch (Exception sendError) + { + outgoingOperation.OperationError = sendError; + throw; + } + } + } +} diff --git a/src/NServiceBus.IntegrationTesting/InternalsVisibleTo.cs b/src/NServiceBus.IntegrationTesting/InternalsVisibleTo.cs new file mode 100644 index 00000000..42bb0b40 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/InternalsVisibleTo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("NServiceBus.IntegrationTesting.Tests")] \ No newline at end of file diff --git a/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs b/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs new file mode 100644 index 00000000..70aa5658 --- /dev/null +++ b/src/NServiceBus.IntegrationTesting/OutgoingMessages.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace NServiceBus.IntegrationTesting +{ + public abstract class OutgoingMessageOperation + { + public string SenderEndpoint { get; internal set; } + public string MessageId { get; internal set; } + public Type MessageType { get; internal set; } + public object MessageInstance { get; internal set; } + public Dictionary MessageHeaders { get; internal set; } + public Exception OperationError { get; internal set; } + } + + public class SendOperation : OutgoingMessageOperation { } + + public class ReplyOperation : OutgoingMessageOperation { } + + public class PublishOperation : OutgoingMessageOperation { } + + public class RequestTimeoutOperation : SendOperation + { + public string SagaId { get; internal set; } + public string SagaTypeAssemblyQualifiedName { get; internal set; } + } +}