Skip to content

Commit b1faaa2

Browse files
Merge pull request #96 from Particular/hotfix-6.0.1
Handle inherited messages correctly
2 parents a942356 + f351fd9 commit b1faaa2

File tree

7 files changed

+238
-39
lines changed

7 files changed

+238
-39
lines changed

src/NServiceBus.Testing.Tests/Handler/HandlerTests.cs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,47 @@ public void OnMessageShouldAwaitAsyncTasks()
8989
.ExpectSend<Send1>(m => true)
9090
.OnMessage<MyCommand>();
9191
}
92+
93+
[Test]
94+
public void ShouldHandleInterfaceImplementingMessages()
95+
{
96+
var handler = new InterfaceMessageHandler();
97+
Test.Handler(handler)
98+
.OnMessage(new InterfaceImplementingMessage());
99+
100+
Assert.IsTrue(handler.HandlerInvoked);
101+
}
102+
103+
public void ShouldHandleBaseClassImplementingMessages()
104+
{
105+
var handler = new InterfaceMessageHandler();
106+
Test.Handler(handler)
107+
.OnMessage(new BaseClassImplementingMessage());
108+
109+
Assert.IsTrue(handler.HandlerInvoked);
110+
}
111+
112+
[Test]
113+
public void ShouldInvokeAllHandlerMethodsWhenHandlingSubclassedMessage()
114+
{
115+
var handler = new MessageHierarchyHandler();
116+
Test.Handler(handler)
117+
.OnMessage(new BaseClassImplementingMessage());
118+
119+
Assert.IsTrue(handler.BaseClassMessageHandlerInvoked);
120+
Assert.IsTrue(handler.BaseClassImplementingMessageHandlerInvoked);
121+
}
122+
123+
[Test]
124+
public void ShouldOnlyInvokeBaseClassHandlerMethofWhenHandlingBaseClassMessage()
125+
{
126+
var handler = new MessageHierarchyHandler();
127+
Test.Handler(handler)
128+
.OnMessage(new BaseClassMessage());
129+
130+
Assert.IsTrue(handler.BaseClassMessageHandlerInvoked);
131+
Assert.IsFalse(handler.BaseClassImplementingMessageHandlerInvoked);
132+
}
92133
}
93134

94135
class MyCommand : ICommand
@@ -227,4 +268,66 @@ Task IHandleMessages<TestMessage>.Handle(TestMessage message, IMessageHandlerCon
227268
return Task.FromResult(0);
228269
}
229270
}
271+
272+
public class InterfaceMessageHandler : IHandleMessages<IMessageInterface>
273+
{
274+
public bool HandlerInvoked { get; private set; }
275+
276+
public Task Handle(IMessageInterface message, IMessageHandlerContext context)
277+
{
278+
HandlerInvoked = true;
279+
280+
return Task.FromResult(0);
281+
}
282+
}
283+
284+
public interface IMessageInterface : IMessage
285+
{
286+
}
287+
288+
public class InterfaceImplementingMessage : IMessageInterface
289+
{
290+
}
291+
292+
public class BaseClassMessageHandler : IHandleMessages<BaseClassMessage>
293+
{
294+
public bool HandlerInvoked { get; private set; }
295+
296+
public Task Handle(BaseClassMessage message, IMessageHandlerContext context)
297+
{
298+
HandlerInvoked = true;
299+
300+
return Task.FromResult(0);
301+
}
302+
}
303+
304+
public class MessageHierarchyHandler :
305+
IHandleMessages<BaseClassMessage>,
306+
IHandleMessages<BaseClassImplementingMessage>
307+
{
308+
public bool BaseClassMessageHandlerInvoked { get; private set; }
309+
public bool BaseClassImplementingMessageHandlerInvoked { get; private set; }
310+
311+
public Task Handle(BaseClassMessage message, IMessageHandlerContext context)
312+
{
313+
BaseClassMessageHandlerInvoked = true;
314+
315+
return Task.FromResult(0);
316+
}
317+
318+
public Task Handle(BaseClassImplementingMessage message, IMessageHandlerContext context)
319+
{
320+
BaseClassImplementingMessageHandlerInvoked = true;
321+
322+
return Task.FromResult(0);
323+
}
324+
}
325+
326+
public class BaseClassMessage : IMessage
327+
{
328+
}
329+
330+
public class BaseClassImplementingMessage : BaseClassMessage
331+
{
332+
}
230333
}

src/NServiceBus.Testing.Tests/Saga/SagaTests.cs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,47 @@ public void ConfigureHandlerContext()
9696
Assert.AreEqual(replyToAddress, receivedContextInstance.ReplyToAddress);
9797
Assert.AreSame(receivedContextInstance, configuredContextInstance);
9898
}
99+
100+
[Test]
101+
public void ShouldInvokeAllHandlerMethodsWhenHandlingSubclassedMessage()
102+
{
103+
var saga = new MessageHierarchySaga();
104+
Test.Saga(saga)
105+
.WhenHandling<BaseClassImplementingMessage>();
106+
107+
Assert.IsTrue(saga.BaseClassMessageHandlerInvoked);
108+
Assert.IsTrue(saga.BaseClassImplementingMessageHandlerInvoked);
109+
}
110+
111+
[Test]
112+
public void ShouldOnlyInvokeBaseClassHandlerMethofWhenHandlingBaseClassMessage()
113+
{
114+
var saga = new MessageHierarchySaga();
115+
Test.Saga(saga)
116+
.WhenHandling<BaseClassMessage>();
117+
118+
Assert.IsTrue(saga.BaseClassMessageHandlerInvoked);
119+
Assert.IsFalse(saga.BaseClassImplementingMessageHandlerInvoked);
120+
}
121+
122+
[Test]
123+
public void ShouldInvokeBaseClassHandlerForSubclassedMessages()
124+
{
125+
var handlerInvoked = false;
126+
var saga = new CustomSaga<IMessageInterface, MySagaData>
127+
{
128+
HandlerAction = (request, context, data) =>
129+
{
130+
handlerInvoked = true;
131+
return Task.FromResult(0);
132+
}
133+
};
134+
135+
Test.Saga(saga)
136+
.WhenHandling<InterfaceImplementingMessage>();
137+
138+
Assert.IsTrue(handlerInvoked);
139+
}
99140
}
100141

101142
public class MyRequest
@@ -194,4 +235,47 @@ protected override void ConfigureHowToFindSaga(SagaPropertyMapper<TSagaData> map
194235
{
195236
}
196237
}
238+
239+
public class MessageHierarchySaga :
240+
NServiceBus.Saga<MySagaData>,
241+
IAmStartedByMessages<BaseClassMessage>,
242+
IAmStartedByMessages<BaseClassImplementingMessage>
243+
{
244+
public bool BaseClassMessageHandlerInvoked { get; private set; }
245+
public bool BaseClassImplementingMessageHandlerInvoked { get; private set; }
246+
247+
public Task Handle(BaseClassMessage message, IMessageHandlerContext context)
248+
{
249+
BaseClassMessageHandlerInvoked = true;
250+
251+
return Task.FromResult(0);
252+
}
253+
254+
public Task Handle(BaseClassImplementingMessage message, IMessageHandlerContext context)
255+
{
256+
BaseClassImplementingMessageHandlerInvoked = true;
257+
258+
return Task.FromResult(0);
259+
}
260+
261+
protected override void ConfigureHowToFindSaga(SagaPropertyMapper<MySagaData> mapper)
262+
{
263+
}
264+
}
265+
266+
public class BaseClassMessage : IMessage
267+
{
268+
}
269+
270+
public class BaseClassImplementingMessage : BaseClassMessage
271+
{
272+
}
273+
274+
public interface IMessageInterface : IMessage
275+
{
276+
}
277+
278+
public class InterfaceImplementingMessage : IMessageInterface
279+
{
280+
}
197281
}

src/NServiceBus.Testing/Handler.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,13 +301,10 @@ public void OnMessage<TMessage>(TMessage message, string messageId)
301301
public void OnMessage<TMessage>(TMessage initializedMessage)
302302
{
303303
var messageType = messageCreator.GetMappedTypeFor(initializedMessage.GetType());
304-
var handleMethod = handler.GetType().CreateInvoker(messageType, typeof(IHandleMessages<>));
305-
if (handleMethod == null)
306-
{
307-
return;
308-
}
304+
var handleMethods = handler.GetType().CreateInvokers(messageType, typeof(IHandleMessages<>));
305+
306+
handleMethods.InvokeSerially(handler, initializedMessage, testableMessageHandlerContext).GetAwaiter().GetResult();
309307

310-
handleMethod(handler, initializedMessage, testableMessageHandlerContext).GetAwaiter().GetResult();
311308
testableMessageHandlerContext.Validate();
312309
}
313310

src/NServiceBus.Testing/NServiceBus.Testing.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="12.0">
3+
<Import Project="..\packages\Particular.CodeRules.0.1.1\build\Particular.CodeRules.props" Condition="Exists('..\packages\Particular.CodeRules.0.1.1\build\Particular.CodeRules.props')" />
34
<PropertyGroup>
45
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
56
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
@@ -135,6 +136,9 @@
135136
<ItemGroup>
136137
<Content Include="FodyWeavers.xml" />
137138
</ItemGroup>
139+
<ItemGroup>
140+
<Analyzer Include="..\packages\Particular.CodeRules.0.1.1\tools\..\analyzers\dotnet\cs\Particular.CodeRules.dll" />
141+
</ItemGroup>
138142
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
139143
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
140144
<PropertyGroup>
@@ -143,6 +147,7 @@
143147
<Error Condition="!Exists('..\packages\Fody.1.29.4\build\dotnet\Fody.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Fody.1.29.4\build\dotnet\Fody.targets'))" />
144148
<Error Condition="!Exists('..\packages\NuGetPackager.0.6.0\build\NuGetPackager.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\NuGetPackager.0.6.0\build\NuGetPackager.targets'))" />
145149
<Error Condition="!Exists('..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\GitVersionTask.3.6.3\build\dotnet\GitVersionTask.targets'))" />
150+
<Error Condition="!Exists('..\packages\Particular.CodeRules.0.1.1\build\Particular.CodeRules.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Particular.CodeRules.0.1.1\build\Particular.CodeRules.props'))" />
146151
</Target>
147152
<Import Project="..\packages\Fody.1.29.4\build\dotnet\Fody.targets" Condition="Exists('..\packages\Fody.1.29.4\build\dotnet\Fody.targets')" />
148153
<Import Project="..\packages\NuGetPackager.0.6.0\build\NuGetPackager.targets" Condition="Exists('..\packages\NuGetPackager.0.6.0\build\NuGetPackager.targets')" />

src/NServiceBus.Testing/Saga.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,9 @@ public Saga<T> ExpectHandleCurrentMessageLater()
259259
public Saga<T> WhenHandling<TMessage>(Action<TMessage> initializeMessage = null)
260260
{
261261
var message = messageCreator.CreateInstance(initializeMessage);
262-
var invoker = saga.GetType().CreateInvoker(typeof(TMessage), typeof(IHandleMessages<>));
262+
var invokers = saga.GetType().CreateInvokers(typeof(TMessage), typeof(IHandleMessages<>));
263263

264-
return When((s, c) => invoker(saga, message, c));
264+
return When((s, c) => invokers.InvokeSerially(saga, message, c));
265265
}
266266

267267
/// <summary>
@@ -271,9 +271,9 @@ public Saga<T> WhenHandling<TMessage>(Action<TMessage> initializeMessage = null)
271271
public Saga<T> WhenHandlingTimeout<TMessage>(Action<TMessage> initializeMessage = null)
272272
{
273273
var message = messageCreator.CreateInstance(initializeMessage);
274-
var invoker = saga.GetType().CreateInvoker(typeof(TMessage), typeof(IHandleTimeouts<>));
274+
var invokers = saga.GetType().CreateInvokers(typeof(TMessage), typeof(IHandleTimeouts<>));
275275

276-
return When((s, c) => invoker(saga, message, c));
276+
return When((s, c) => invokers.InvokeSerially(saga, message, c));
277277
}
278278

279279
/// <summary>
@@ -539,8 +539,8 @@ void InvokeTimeouts(IEnumerable<TimeoutMessage<object>> messages)
539539
.ForEach(t =>
540540
{
541541
var messageType = messageCreator.GetMappedTypeFor(t.Message.GetType());
542-
var invoker = saga.GetType().CreateInvoker(messageType, typeof(IHandleTimeouts<>));
543-
invoker(saga, t.Message, testContext).GetAwaiter().GetResult();
542+
var invokers = saga.GetType().CreateInvokers(messageType, typeof(IHandleTimeouts<>));
543+
invokers.InvokeSerially(saga, t.Message, testContext).GetAwaiter().GetResult();
544544
});
545545

546546
testContext.Validate();
Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,48 @@
11
namespace NServiceBus.Testing
22
{
33
using System;
4+
using System.Collections.Generic;
45
using System.Linq;
56
using System.Linq.Expressions;
67
using System.Threading.Tasks;
7-
8+
89
static class TypeExtensions
910
{
10-
public static Func<object, object, IMessageHandlerContext, Task> CreateInvoker(this Type targetType, Type messageType, Type interfaceGenericType)
11+
public static IEnumerable<Func<object, object, IMessageHandlerContext, Task>> CreateInvokers(this Type targetType, Type messageType, Type interfaceGenericType)
1112
{
12-
var interfaceType = interfaceGenericType.MakeGenericType(messageType);
13-
14-
if (!interfaceType.IsAssignableFrom(targetType))
13+
var interfaceTypes = targetType.GetInterfaces()
14+
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceGenericType)
15+
.Where(i => i.GenericTypeArguments.First().IsAssignableFrom(messageType));
16+
17+
foreach (var interfaceType in interfaceTypes)
1518
{
16-
return null;
17-
}
18-
19-
var methodInfo = targetType.GetInterfaceMap(interfaceType).TargetMethods.FirstOrDefault();
20-
if (methodInfo == null)
21-
{
22-
return null;
23-
}
24-
25-
var target = Expression.Parameter(typeof(object));
26-
var messageParam = Expression.Parameter(typeof(object));
27-
var contextParam = Expression.Parameter(typeof(IMessageHandlerContext));
28-
29-
var castTarget = Expression.Convert(target, targetType);
30-
31-
var methodParameters = methodInfo.GetParameters();
32-
var messageCastParam = Expression.Convert(messageParam, methodParameters.ElementAt(0).ParameterType);
33-
34-
Expression body = Expression.Call(castTarget, methodInfo, messageCastParam, contextParam);
35-
36-
return Expression.Lambda<Func<object, object, IMessageHandlerContext, Task>>(body, target, messageParam, contextParam).Compile();
19+
var methodInfo = targetType.GetInterfaceMap(interfaceType).TargetMethods.FirstOrDefault();
20+
if (methodInfo == null)
21+
{
22+
yield break;
23+
}
24+
25+
var target = Expression.Parameter(typeof(object));
26+
var messageParam = Expression.Parameter(typeof(object));
27+
var contextParam = Expression.Parameter(typeof(IMessageHandlerContext));
28+
29+
var castTarget = Expression.Convert(target, targetType);
30+
31+
var methodParameters = methodInfo.GetParameters();
32+
var messageCastParam = Expression.Convert(messageParam, methodParameters.ElementAt(0).ParameterType);
33+
34+
Expression body = Expression.Call(castTarget, methodInfo, messageCastParam, contextParam);
35+
36+
yield return Expression.Lambda<Func<object, object, IMessageHandlerContext, Task>>(body, target, messageParam, contextParam).Compile();
37+
}
3738
}
39+
40+
public static async Task InvokeSerially(this IEnumerable<Func<object, object, IMessageHandlerContext, Task>> invokers, object instance, object message, IMessageHandlerContext context)
41+
{
42+
foreach (var invocation in invokers)
43+
{
44+
await invocation(instance, message, context).ConfigureAwait(false);
45+
}
46+
}
3847
}
39-
}
48+
}

src/NServiceBus.Testing/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
<package id="NServiceBus.Testing.Fakes.Sources" version="6.0.0" targetFramework="net452" />
77
<package id="NuGetPackager" version="0.6.0" targetFramework="net452" developmentDependency="true" />
88
<package id="Obsolete.Fody" version="4.1.0" targetFramework="net452" developmentDependency="true" />
9+
<package id="Particular.CodeRules" version="0.1.1" targetFramework="net452" developmentDependency="true" />
910
</packages>

0 commit comments

Comments
 (0)