Skip to content

Commit 7fa0e32

Browse files
committed
ComMock.Verify works!
1 parent 438c450 commit 7fa0e32

File tree

7 files changed

+190
-48
lines changed

7 files changed

+190
-48
lines changed

Rubberduck.Main/ComClientLibrary/UnitTesting/Mocks/ComMock.cs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,20 +153,36 @@ public void Verify(string Name, ITimes Times, [MarshalAs(UnmanagedType.Struct),
153153
var args = _resolver.ResolveArgs(Args);
154154
var setupDatas = _setupBuilder.CreateExpression(Name, args);
155155

156-
try
156+
var throwingExecutions = 0;
157+
MockException lastException = null;
158+
foreach (var setupData in setupDatas)
157159
{
158-
foreach (var setupData in setupDatas)
160+
try
159161
{
160162
var builder = MockExpressionBuilder.Create(Mock);
161163
builder.As(setupData.DeclaringType)
162-
.Verify(setupData.SetupExpression, setupData.Args)
164+
.Verify(setupData.SetupExpression, Times, setupData.Args)
163165
.Execute();
166+
167+
Rubberduck.UnitTesting.AssertHandler.OnAssertSucceeded();
168+
}
169+
catch (TargetInvocationException exception)
170+
{
171+
if (exception.InnerException is MockException inner)
172+
{
173+
throwingExecutions++;
174+
lastException = inner;
175+
}
176+
else
177+
{
178+
throw;
179+
}
164180
}
165181
}
166-
catch (MockException)
182+
if (setupDatas.Count() == throwingExecutions)
167183
{
168-
// todo move to resx if this works.. and account for the several possible wordings
169-
Rubberduck.UnitTesting.AssertHandler.OnAssertFailed($"Member '{Name}' was invoked {Mock.Invocations.Count(e => e.Method.Name == Name)} times; expected {Times}.");
184+
// if all mocked interfaces failed the .Verify call, then none of them succeeded:
185+
Rubberduck.UnitTesting.AssertHandler.OnAssertFailed(lastException.Message);
170186
}
171187
}
172188

Rubberduck.Main/ComClientLibrary/UnitTesting/Mocks/ITimes.cs

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -37,54 +37,48 @@ public interface ITimes
3737
}
3838

3939

40+
public static class MoqTimesExt
41+
{
42+
public static ITimes ToRubberduckTimes(this Moq.Times times) => new Times(times);
43+
}
44+
4045
[ComVisible(true)]
4146
[Guid(RubberduckGuid.TimesGuid)]
4247
[ProgId(RubberduckProgId.TimesProgId)]
4348
[ClassInterface(ClassInterfaceType.None)]
4449
[ComDefaultInterface(typeof(ITimes))]
4550
public class Times : ITimes, IEquatable<Times>
4651
{
47-
internal int Min { get; }
48-
internal int Max { get; }
49-
5052
internal Times() { }
5153

52-
internal Times(int min, int max)
54+
internal Times(Moq.Times moqTimes)
5355
{
54-
Min = min;
55-
Max = max;
56+
MoqTimes = moqTimes;
5657
}
5758

58-
public bool Equals(Times other) => other.Min == Min && other.Max == Max;
59+
public bool Equals(Times other) => MoqTimes.Equals(other?.MoqTimes);
5960

60-
public override bool Equals(object obj) => Equals((Times)obj);
61+
public override bool Equals(object obj) => Equals(obj as Times);
6162

62-
public override int GetHashCode() => HashCode.Compute(Min, Max);
63+
public override int GetHashCode() => MoqTimes.GetHashCode();
6364

64-
public ITimes AtMost(int CallCount) => new Times(0, CallCount);
65+
public ITimes AtMost(int CallCount) => new Times(Moq.Times.AtMost(CallCount));
6566

66-
public ITimes AtMostOnce() => new Times(0, 1);
67+
public ITimes AtMostOnce() => new Times(Moq.Times.AtMostOnce());
6768

68-
public ITimes AtLeast(int CallCount) => new Times(CallCount, int.MaxValue);
69+
public ITimes AtLeast(int CallCount) => new Times(Moq.Times.AtLeast(CallCount));
6970

70-
public ITimes AtLeastOnce() => new Times(1, int.MaxValue);
71+
public ITimes AtLeastOnce() => new Times(Moq.Times.AtLeastOnce());
7172

7273
public ITimes Between(int MinCallCount, int MaxCallCount, SetupArgumentRange RangeKind = SetupArgumentRange.Inclusive)
73-
=> new Times(MinCallCount + RangeKind == SetupArgumentRange.Exclusive ? 1 : 0, MaxCallCount - RangeKind == SetupArgumentRange.Exclusive ? 1 : 0);
74+
=> new Times(Moq.Times.Between(MinCallCount, MaxCallCount, RangeKind == SetupArgumentRange.Exclusive ? Moq.Range.Exclusive : Moq.Range.Inclusive ));
7475

75-
public ITimes Exactly(int CallCount) => new Times(CallCount, CallCount);
76+
public ITimes Exactly(int CallCount) => new Times(Moq.Times.Exactly(CallCount));
7677

77-
public ITimes Once() => new Times(1, 1);
78+
public ITimes Once() => new Times(Moq.Times.Once());
7879

79-
public ITimes Never() => new Times(0, 0);
80+
public ITimes Never() => new Times(Moq.Times.Never());
8081

81-
public static bool operator ==(Times lhs, Times rhs) => lhs.Equals(rhs);
82-
public static bool operator !=(Times lhs, Times rhs) => !lhs.Equals(rhs);
83-
84-
public void Deconstruct(out int MinCallCount, out int MaxCallCount)
85-
{
86-
MinCallCount = Min;
87-
MaxCallCount = Max;
88-
}
82+
internal Moq.Times MoqTimes { get; }
8983
}
9084
}

Rubberduck.Main/ComClientLibrary/UnitTesting/Mocks/MockExpressionBuilder.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public interface IRuntimeSetup : IRuntimeMock, IRuntimeCallback, IRuntimeExecute
2020

2121
public interface IRuntimeVerify : IRuntimeExecute
2222
{
23-
IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs);
24-
IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType);
23+
IRuntimeVerify Verify(Expression verifyExpression, ITimes times, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs);
24+
IRuntimeVerify Verify(Expression verifyExpression, ITimes times, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType);
2525
}
2626

2727
public interface IRuntimeCallback : IRuntimeExecute
@@ -163,18 +163,19 @@ public object Execute()
163163
: Expression.Lambda(_expression, _mockParameterExpression).Compile().DynamicInvoke(args);
164164
}
165165

166-
public IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs)
166+
public IRuntimeVerify Verify(Expression verifyExpression, ITimes times, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs)
167167
{
168168
switch (verifyExpression.Type.GetGenericArguments().Length)
169169
{
170170
case 2:
171-
// It's a returning method so we need to use the Func version of Setup and ignore the return.
172-
Verify(verifyExpression, forwardedArgs, verifyExpression.Type.GetGenericArguments()[1]);
171+
// It's a returning method so we need to use the Func overload of Setup and ignore the return.
172+
Verify(verifyExpression, times, forwardedArgs, verifyExpression.Type.GetGenericArguments()[1]);
173173
return this;
174174
case 1:
175-
var verifyMethodInfo = MockMemberInfos.Verify(_currentType, null);
175+
var verifyMethodInfo = MockMemberInfos.Verify(_currentType);
176+
var rdTimes = (Times)times;
176177
// Quoting the setup lambda expression ensures that closures will be applied
177-
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression));
178+
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression), Expression.Constant(rdTimes.MoqTimes));
178179
_currentType = verifyMethodInfo.ReturnType;
179180
if (forwardedArgs.Any())
180181
{
@@ -187,11 +188,12 @@ public IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<Pa
187188
}
188189
}
189190

190-
public IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType)
191+
public IRuntimeVerify Verify(Expression verifyExpression, ITimes times, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType)
191192
{
192-
var verifyMethodInfo = MockMemberInfos.Setup(_currentType, returnType);
193+
var verifyMethodInfo = MockMemberInfos.Verify(_currentType, returnType);
194+
var rdTimes = (Times)times;
193195
// Quoting the verify lambda expression ensures that closures will be applied
194-
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression));
196+
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression), Expression.Constant(rdTimes.MoqTimes));
195197
_currentType = verifyMethodInfo.ReturnType;
196198
if (forwardedArgs.Any())
197199
{

Rubberduck.Main/ComClientLibrary/UnitTesting/Mocks/MockReflection.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,30 @@ public static MethodInfo As(Type type)
2222
return Reflection.GetMethodExt(typeof(Mock), MockMemberNames.As()).MakeGenericMethod(type);
2323
}
2424

25+
public static MethodInfo Verify(Type mockType)
26+
{
27+
var typeHandle = mockType.TypeHandle;
28+
var mock = typeof(Mock<>);
29+
30+
var actionArgExpression = typeof(Expression<>).MakeGenericType(typeof(Action<>));
31+
var genericMethod = Reflection.GetMethodExt(mock, MockMemberNames.Verify(), actionArgExpression, typeof(Moq.Times));
32+
var specificMethod = (MethodInfo)MethodBase.GetMethodFromHandle(genericMethod.MethodHandle, typeHandle);
33+
34+
return specificMethod;
35+
}
36+
2537
public static MethodInfo Verify(Type mockType, Type returnType)
2638
{
2739
var typeHandle = mockType.TypeHandle;
2840
var mock = typeof(Mock<>);
29-
var expression = typeof(Expression<>).MakeGenericType(returnType != null ?
41+
42+
var funcArgExpression = typeof(Expression<>).MakeGenericType(returnType != null ?
3043
typeof(Func<,>) :
3144
typeof(Action<>)
3245
);
33-
var genericMethod = Reflection.GetMethodExt(mock, MockMemberNames.Verify(), expression);
46+
var genericMethod = Reflection.GetMethodExt(mock, MockMemberNames.Verify(), funcArgExpression, typeof(Moq.Times));
3447
var specificMethod = (MethodInfo)MethodBase.GetMethodFromHandle(genericMethod.MethodHandle, typeHandle);
48+
3549
return returnType != null ? specificMethod.MakeGenericMethod(returnType) : specificMethod;
3650
}
3751

RubberduckTests/ComMock/ItByRefTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using System.Linq.Expressions;
54
using Moq;
65
using NUnit.Framework;
76
using Rubberduck.ComClientLibrary.UnitTesting.Mocks;
7+
using Times = Moq.Times;
88

99
namespace RubberduckTests.ComMock
1010
{

RubberduckTests/ComMock/MockExpressionBuilderTests.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void Setup_Void_Method_Compiles()
3434
}
3535

3636
[Test]
37-
public void Setup_Returning_Method_With_Return_Ignored_Compiles()
37+
public void Setup_ReturningMethod_WithReturnIgnored_Compiles()
3838
{
3939
var mock = new Mock<ITest1>();
4040
var builder = MockExpressionBuilder.Create(mock);
@@ -46,7 +46,7 @@ public void Setup_Returning_Method_With_Return_Ignored_Compiles()
4646
}
4747

4848
[Test]
49-
public void Setup_Returning_Method_Compiles()
49+
public void Setup_ReturningMethod_Compiles()
5050
{
5151
var mock = new Mock<ITest1>();
5252
var builder = MockExpressionBuilder.Create(mock);
@@ -58,7 +58,7 @@ public void Setup_Returning_Method_Compiles()
5858
}
5959

6060
[Test]
61-
public void Setup_With_Returns_Compiles()
61+
public void SetupWithReturns_Compiles()
6262
{
6363
const int expected = 42;
6464
var mock = new Mock<ITest1>();
@@ -74,7 +74,7 @@ public void Setup_With_Returns_Compiles()
7474
}
7575

7676
[Test]
77-
public void Setup_With_Callback_Compiles()
77+
public void SetupWithCallback_Compiles()
7878
{
7979
const int expected = 42;
8080
var actual = 0;
@@ -93,13 +93,33 @@ public void Setup_With_Callback_Compiles()
9393
Assert.AreEqual(expected, actual);
9494
}
9595

96+
[Test]
97+
public void Verify_Compiles()
98+
{
99+
const int expected = 1;
100+
var expectedTimes = Moq.Times.Once().ToRubberduckTimes();
101+
102+
var mock = new Mock<ITest1>();
103+
var builder = MockExpressionBuilder.Create(mock);
104+
var expression = ArrangeSetupDoExpression();
105+
106+
builder.As(typeof(ITest1))
107+
.Verify(expression, expectedTimes, ArrangeForwardedArgs())
108+
.Execute();
109+
110+
mock.Object.Do();
111+
Assert.AreEqual(expected, mock.Invocations.Count);
112+
}
113+
96114
private static IReadOnlyDictionary<ParameterExpression, object> ArrangeForwardedArgs()
97115
{
98116
return new Dictionary<ParameterExpression, object>();
99117
}
100118

101119
private static Expression ArrangeSetupDoExpression()
102120
{
121+
// x => x.Do()
122+
103123
var typeParameterExpression = Expression.Parameter(typeof(ITest1), "x");
104124
var methodInfo = typeof(ITest1).GetMethod(nameof(ITest1.Do));
105125
var callExpression = Expression.Call(typeParameterExpression, methodInfo);
@@ -108,6 +128,8 @@ private static Expression ArrangeSetupDoExpression()
108128

109129
private static Expression ArrangeSetupDoThisExpression()
110130
{
131+
// x => x.DoThis()
132+
111133
var typeParameterExpression = Expression.Parameter(typeof(ITest1), "x");
112134
var methodInfo = typeof(ITest1).GetMethod(nameof(ITest1.DoThis));
113135
var callExpression = Expression.Call(typeParameterExpression, methodInfo);

RubberduckTests/ComMock/MockProviderTests.cs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,100 @@ public void Mock_Setup_Property_Specified_Args_Returns_Specified_Value(string II
537537
}
538538
}
539539

540+
[Test]
541+
[TestCase(IID_FileSystem3_String)]
542+
[TestCase(IID_FileSystem_String)]
543+
public void Mock_Verify_NoSetup_NoOp(string IID)
544+
{
545+
var pUnk = IntPtr.Zero;
546+
var pProxy = IntPtr.Zero;
547+
548+
const int expectedAssertsCompleted = 1;
549+
const Rubberduck.UnitTesting.TestOutcome expectedOutcome = Rubberduck.UnitTesting.TestOutcome.Succeeded;
550+
var assertsCompleted = 0;
551+
Rubberduck.UnitTesting.TestOutcome outcome = Rubberduck.UnitTesting.TestOutcome.Unknown;
552+
var handleAssertCompleted = new EventHandler<Rubberduck.UnitTesting.AssertCompletedEventArgs>((o, e) => { outcome = e.Outcome; assertsCompleted++; });
553+
554+
try
555+
{
556+
Rubberduck.UnitTesting.AssertHandler.OnAssertCompleted += handleAssertCompleted;
557+
558+
var provider = new MockProvider();
559+
var mockFso = provider.Mock("Scripting.FileSystemObject");
560+
var obj = mockFso.Object;
561+
562+
pUnk = Marshal.GetIUnknownForObject(obj);
563+
564+
var iid = new Guid(IID);
565+
var hr = Marshal.QueryInterface(pUnk, ref iid, out pProxy);
566+
if (hr != 0)
567+
{
568+
throw new InvalidCastException("QueryInterface failed on the proxy type");
569+
}
570+
571+
dynamic mocked = Marshal.GetObjectForIUnknown(pProxy);
572+
Assert.IsNull(mocked.Drives, "Expected null reference for not-setup object property.");
573+
574+
mockFso.Verify("Drives", provider.Times.Never());
575+
Assert.AreEqual(expectedAssertsCompleted, assertsCompleted, "Expected asserts mismatched.");
576+
Assert.AreEqual(expectedOutcome, outcome, "Expected test outcome mismatched.");
577+
}
578+
finally
579+
{
580+
if (pProxy != IntPtr.Zero) Marshal.Release(pProxy);
581+
if (pUnk != IntPtr.Zero) Marshal.Release(pUnk);
582+
Rubberduck.UnitTesting.AssertHandler.OnAssertCompleted -= handleAssertCompleted;
583+
}
584+
}
585+
586+
[Test]
587+
[TestCase(IID_FileSystem3_String)]
588+
[TestCase(IID_FileSystem_String)]
589+
public void Mock_Verify_WithSetup_WithExpectedInvocationCount_Passes(string IID)
590+
{
591+
var pUnk = IntPtr.Zero;
592+
var pProxy = IntPtr.Zero;
593+
594+
const int expectedAssertsCompleted = 1;
595+
const Rubberduck.UnitTesting.TestOutcome expectedOutcome = Rubberduck.UnitTesting.TestOutcome.Succeeded;
596+
var assertsCompleted = 0;
597+
Rubberduck.UnitTesting.TestOutcome outcome = Rubberduck.UnitTesting.TestOutcome.Unknown;
598+
var handleAssertCompleted = new EventHandler<Rubberduck.UnitTesting.AssertCompletedEventArgs>((o, e) => { outcome = e.Outcome; assertsCompleted++; });
599+
600+
try
601+
{
602+
Rubberduck.UnitTesting.AssertHandler.OnAssertCompleted += handleAssertCompleted;
603+
604+
var provider = new MockProvider();
605+
var mockFso = provider.Mock("Scripting.FileSystemObject");
606+
var mockDrives = mockFso.SetupChildMock("Drives");
607+
var obj = mockFso.Object;
608+
609+
pUnk = Marshal.GetIUnknownForObject(obj);
610+
611+
var iid = new Guid(IID);
612+
var hr = Marshal.QueryInterface(pUnk, ref iid, out pProxy);
613+
if (hr != 0)
614+
{
615+
throw new InvalidCastException("QueryInterface failed on the proxy type");
616+
}
617+
618+
dynamic mocked = Marshal.GetObjectForIUnknown(pProxy);
619+
Assert.IsNotNull(mocked.Drives, "Expected non-null reference for set-up object property.");
620+
621+
mockFso.Verify("Drives", provider.Times.Once());
622+
Assert.AreEqual(expectedAssertsCompleted, assertsCompleted, "Expected asserts mismatched.");
623+
624+
Assert.AreEqual(expectedOutcome, outcome, "Expected test outcome mismatched.");
625+
}
626+
finally
627+
{
628+
if (pProxy != IntPtr.Zero) Marshal.Release(pProxy);
629+
if (pUnk != IntPtr.Zero) Marshal.Release(pUnk);
630+
Rubberduck.UnitTesting.AssertHandler.OnAssertCompleted -= handleAssertCompleted;
631+
}
632+
}
633+
540634
/* Commented to remove the PIA reference to Scripting library, but keeping code in one day they fix type equivalence?
541635
[Test]
542636
public void Type_From_ITypeInfo_Are_Equivalent()

0 commit comments

Comments
 (0)