Skip to content

Commit 438c450

Browse files
committed
added initial (broken) implementation for IComMock.Verify
1 parent 8662997 commit 438c450

File tree

9 files changed

+230
-9
lines changed

9 files changed

+230
-9
lines changed

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ internal ComMock(IMockProviderInternal provider, string project, string progId,
4242

4343
public string ProgId { get; }
4444

45+
/// <remarks>
46+
/// Refer to remarks in <see cref="SetupArgumentResolver.ResolveArgs"/> for how the
47+
/// parameter <paramref name="Args"/> is handled.
48+
/// </remarks>
49+
public void Setup(string Name, object Args = null)
50+
{
51+
var args = _resolver.ResolveArgs(Args);
52+
var setupDatas = _setupBuilder.CreateExpression(Name, args);
53+
54+
foreach (var setupData in setupDatas)
55+
{
56+
var builder = MockExpressionBuilder.Create(Mock);
57+
builder.As(setupData.DeclaringType)
58+
.Setup(setupData.SetupExpression, setupData.Args)
59+
.Execute();
60+
}
61+
}
62+
4563
/// <remarks>
4664
/// Refer to remarks in <see cref="SetupArgumentResolver.ResolveArgs"/> for how the
4765
/// parameter <paramref name="Args"/> is handled.
@@ -130,6 +148,28 @@ private object GetMockedObject(IComMock mock, Type type)
130148
}
131149
}
132150

151+
public void Verify(string Name, ITimes Times, [MarshalAs(UnmanagedType.Struct), Optional] object Args)
152+
{
153+
var args = _resolver.ResolveArgs(Args);
154+
var setupDatas = _setupBuilder.CreateExpression(Name, args);
155+
156+
try
157+
{
158+
foreach (var setupData in setupDatas)
159+
{
160+
var builder = MockExpressionBuilder.Create(Mock);
161+
builder.As(setupData.DeclaringType)
162+
.Verify(setupData.SetupExpression, setupData.Args)
163+
.Execute();
164+
}
165+
}
166+
catch (MockException)
167+
{
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}.");
170+
}
171+
}
172+
133173
public object Object => mocked;
134174

135175
internal Mock Mock { get; }

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

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.ComponentModel;
23
using System.Runtime.InteropServices;
34
using Rubberduck.Resources.Registration;
45

@@ -14,21 +15,35 @@ namespace Rubberduck.ComClientLibrary.UnitTesting.Mocks
1415
public interface IComMock
1516
{
1617
[DispId(1)]
18+
[Description("Gets the mocked object.")]
1719
object Object { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
1820

1921
[DispId(2)]
20-
void SetupWithReturns(string Name, [MarshalAs(UnmanagedType.Struct)] object Value, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
22+
[Description("Gets the name of the loaded project defining the mocked interface.")]
23+
string Project { get; }
2124

2225
[DispId(3)]
23-
void SetupWithCallback(string Name, [MarshalAs(UnmanagedType.FunctionPtr)] Action Callback, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
26+
[Description("Gets the programmatic name of the mocked interface.")]
27+
string ProgId { get; }
2428

2529
[DispId(4)]
26-
IComMock SetupChildMock(string Name, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
30+
[Description("Specifies a setup on the mocked type for a call to a method that does not return a value.")]
31+
void Setup(string Name, [MarshalAs(UnmanagedType.Struct)] object Args = null);
2732

2833
[DispId(5)]
29-
string Project { get; }
34+
[Description("Specifies a setup on the mocked type for a call to a value-returning method.")]
35+
void SetupWithReturns(string Name, [MarshalAs(UnmanagedType.Struct)] object Value, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
3036

3137
[DispId(6)]
32-
string ProgId { get; }
38+
[Description("Specifies a callback (use the AddressOf operator) to invoke when the method is called that receives the original invocation.")]
39+
void SetupWithCallback(string Name, [MarshalAs(UnmanagedType.FunctionPtr)] Action Callback, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
40+
41+
[DispId(7)]
42+
[Description("Specifies a setup on the mocked type for a call to an object member of the specified object type.")]
43+
IComMock SetupChildMock(string Name, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
44+
45+
[DispId(9)]
46+
[Description("Verifies that a specific invocation matching the given arguments was performed on the mock.")]
47+
void Verify(string Name, ITimes Times, [Optional, MarshalAs(UnmanagedType.Struct)] object Args);
3348
}
3449
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using Rubberduck.Resources.Registration;
2+
using Rubberduck.VBEditor;
3+
using System;
4+
using System.ComponentModel;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Rubberduck.ComClientLibrary.UnitTesting.Mocks
8+
{
9+
[ComVisible(true)]
10+
[Guid(RubberduckGuid.ITimesGuid)]
11+
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
12+
public interface ITimes
13+
{
14+
[Description("Specifies that a mocked method should be invoked CallCount times as maximum.")]
15+
ITimes AtMost(int CallCount);
16+
17+
[Description("Specifies that a mocked method should be invoked one time as maximum.")]
18+
ITimes AtMostOnce();
19+
20+
[Description("Specifies that a mocked method should be invoked CallCount times as minimum.")]
21+
ITimes AtLeast(int CallCount);
22+
23+
[Description("Specifies that a mocked method should be invoked one time as minimum.")]
24+
ITimes AtLeastOnce();
25+
26+
[Description("Specifies that a mocked method should be invoked between MinCallCount and MaxCallCount times.")]
27+
ITimes Between(int MinCallCount, int MaxCallCount, SetupArgumentRange RangeKind = SetupArgumentRange.Inclusive);
28+
29+
[Description("Specifies that a mocked method should be invoked exactly CallCount times.")]
30+
ITimes Exactly(int CallCount);
31+
32+
[Description("Specifies that a mocked method should be invoked exactly one time.")]
33+
ITimes Once();
34+
35+
[Description("Specifies that a mocked method should not be invoked.")]
36+
ITimes Never();
37+
}
38+
39+
40+
[ComVisible(true)]
41+
[Guid(RubberduckGuid.TimesGuid)]
42+
[ProgId(RubberduckProgId.TimesProgId)]
43+
[ClassInterface(ClassInterfaceType.None)]
44+
[ComDefaultInterface(typeof(ITimes))]
45+
public class Times : ITimes, IEquatable<Times>
46+
{
47+
internal int Min { get; }
48+
internal int Max { get; }
49+
50+
internal Times() { }
51+
52+
internal Times(int min, int max)
53+
{
54+
Min = min;
55+
Max = max;
56+
}
57+
58+
public bool Equals(Times other) => other.Min == Min && other.Max == Max;
59+
60+
public override bool Equals(object obj) => Equals((Times)obj);
61+
62+
public override int GetHashCode() => HashCode.Compute(Min, Max);
63+
64+
public ITimes AtMost(int CallCount) => new Times(0, CallCount);
65+
66+
public ITimes AtMostOnce() => new Times(0, 1);
67+
68+
public ITimes AtLeast(int CallCount) => new Times(CallCount, int.MaxValue);
69+
70+
public ITimes AtLeastOnce() => new Times(1, int.MaxValue);
71+
72+
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+
75+
public ITimes Exactly(int CallCount) => new Times(CallCount, CallCount);
76+
77+
public ITimes Once() => new Times(1, 1);
78+
79+
public ITimes Never() => new Times(0, 0);
80+
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+
}
89+
}
90+
}

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

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,18 @@ public interface IRuntimeMock
1212
IRuntimeSetup As(Type targetInterface);
1313
}
1414

15-
public interface IRuntimeSetup : IRuntimeMock, IRuntimeCallback, IRuntimeExecute
15+
public interface IRuntimeSetup : IRuntimeMock, IRuntimeCallback, IRuntimeExecute, IRuntimeVerify
1616
{
1717
IRuntimeCallback Setup(Expression setupExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs);
1818
IRuntimeReturns Setup(Expression setupExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType);
1919
}
2020

21+
public interface IRuntimeVerify : IRuntimeExecute
22+
{
23+
IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs);
24+
IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType);
25+
}
26+
2127
public interface IRuntimeCallback : IRuntimeExecute
2228
{
2329
IRuntimeSetup Callback(Action callback);
@@ -40,6 +46,7 @@ public class MockExpressionBuilder :
4046
IRuntimeSetup,
4147
IRuntimeCallback,
4248
IRuntimeReturns,
49+
IRuntimeVerify,
4350
IRuntimeExecute
4451
{
4552
private readonly Mock _mock;
@@ -155,5 +162,43 @@ public object Execute()
155162
? ((LambdaExpression)_expression).Compile().DynamicInvoke(args)
156163
: Expression.Lambda(_expression, _mockParameterExpression).Compile().DynamicInvoke(args);
157164
}
165+
166+
public IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs)
167+
{
168+
switch (verifyExpression.Type.GetGenericArguments().Length)
169+
{
170+
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]);
173+
return this;
174+
case 1:
175+
var verifyMethodInfo = MockMemberInfos.Verify(_currentType, null);
176+
// Quoting the setup lambda expression ensures that closures will be applied
177+
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression));
178+
_currentType = verifyMethodInfo.ReturnType;
179+
if (forwardedArgs.Any())
180+
{
181+
_lambdaArguments.AddRange(forwardedArgs.Keys);
182+
_args.AddRange(forwardedArgs.Values);
183+
}
184+
return this;
185+
default:
186+
throw new NotSupportedException("Verify can only handle 1 or 2 arguments as an input");
187+
}
188+
}
189+
190+
public IRuntimeVerify Verify(Expression verifyExpression, IReadOnlyDictionary<ParameterExpression, object> forwardedArgs, Type returnType)
191+
{
192+
var verifyMethodInfo = MockMemberInfos.Setup(_currentType, returnType);
193+
// Quoting the verify lambda expression ensures that closures will be applied
194+
_expression = Expression.Call(_expression, verifyMethodInfo, Expression.Quote(verifyExpression));
195+
_currentType = verifyMethodInfo.ReturnType;
196+
if (forwardedArgs.Any())
197+
{
198+
_lambdaArguments.AddRange(forwardedArgs.Keys);
199+
_args.AddRange(forwardedArgs.Values);
200+
}
201+
return this;
202+
}
158203
}
159204
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.ComponentModel;
23
using System.Linq;
34
using System.Reflection;
45
using System.Runtime.InteropServices;
@@ -25,10 +26,16 @@ namespace Rubberduck.ComClientLibrary.UnitTesting.Mocks
2526
public interface IMockProvider
2627
{
2728
[DispId(1)]
29+
[Description("Creates a new mock for the specified interface.")]
2830
IComMock Mock(string ProgId, [Optional] string Project);
2931

3032
[DispId(2)]
33+
[Description("Gets an object that creates argument placeholders for an expression.")]
3134
SetupArgumentCreator It { get; }
35+
36+
[DispId(3)]
37+
[Description("Gets an object that specifies how many times a verifiable invocation should occur.")]
38+
ITimes Times { get; }
3239
}
3340

3441
[ComVisible(false)]
@@ -51,6 +58,7 @@ public class MockProvider : IMockProviderInternal
5158
public MockProvider()
5259
{
5360
It = new SetupArgumentCreator();
61+
Times = new Times();
5462
}
5563

5664
public IComMock Mock(string ProgId, string Project = null)
@@ -111,6 +119,7 @@ private ComMock CreateComMock(string project, string progId, Type classType)
111119
}
112120

113121
public SetupArgumentCreator It { get; }
122+
public ITimes Times { get; }
114123

115124
private static Type GetComDefaultInterface(Type classType)
116125
{

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ 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, Type returnType)
26+
{
27+
var typeHandle = mockType.TypeHandle;
28+
var mock = typeof(Mock<>);
29+
var expression = typeof(Expression<>).MakeGenericType(returnType != null ?
30+
typeof(Func<,>) :
31+
typeof(Action<>)
32+
);
33+
var genericMethod = Reflection.GetMethodExt(mock, MockMemberNames.Verify(), expression);
34+
var specificMethod = (MethodInfo)MethodBase.GetMethodFromHandle(genericMethod.MethodHandle, typeHandle);
35+
return returnType != null ? specificMethod.MakeGenericMethod(returnType) : specificMethod;
36+
}
37+
2538
public static MethodInfo Setup(Type mockType, Type returnType)
2639
{
2740
var typeHandle = mockType.TypeHandle;
@@ -86,5 +99,10 @@ public static string Callback()
8699
{
87100
return nameof(ISetup<object>.Callback);
88101
}
102+
103+
public static string Verify()
104+
{
105+
return nameof(Mock.Verify);
106+
}
89107
}
90108
}

Rubberduck.Parsing/ComReflection/TypeLibReflection/TypeLibQueryService.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
using System;
2-
using System.IO;
2+
using System.IO.Abstractions;
33
using System.Runtime.InteropServices;
44
using System.Runtime.InteropServices.ComTypes;
55
using Microsoft.Win32;
6+
using Rubberduck.InternalApi.Common;
67
using Rubberduck.VBEditor.Utility;
78
using TYPEATTR = System.Runtime.InteropServices.ComTypes.TYPEATTR;
89

@@ -141,7 +142,7 @@ private static bool TryLoadTypeLibFromPath(GetPathFunction getPathFunction, Regi
141142
return true;
142143
}
143144

144-
var file = Path.GetFileName(path);
145+
var file = FileSystemProvider.FileSystem.Path.GetFileName(path);
145146
return LoadTypeLib(file, out lib) == 0;
146147
}
147148

Rubberduck.Resources/Registration/RubberduckGuid.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public static class RubberduckGuid
4141
public const string SetupArgumentCreatorGuid = UnitTestingGuidspace + "EB" + GuidSuffix;
4242
public const string IComMockedGuid = UnitTestingGuidspace + "EC" + GuidSuffix;
4343
public const string ComMockedGuid = UnitTestingGuidspace + "ED" + GuidSuffix;
44+
public const string ITimesGuid = UnitTestingGuidspace + "EE" + GuidSuffix;
45+
public const string TimesGuid = UnitTestingGuidspace + "EF" + GuidSuffix;
4446

4547
// Rubberduck API Guids:
4648
private const string ApiGuidspace = "69E0F7";

Rubberduck.Resources/Registration/RubberduckProgId.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ public static class RubberduckProgId
2222
public const string AssertClassProgId = BaseNamespace + "AssertClass";
2323
public const string PermissiveAssertClassProgId = BaseNamespace + "PermissiveAssertClass";
2424
public const string FakesProviderProgId = BaseNamespace + "FakesProvider";
25-
25+
public const string TimesProgId = BaseNamespace + "Times";
26+
2627
public const string DebugAddinObject = BaseNamespace + "VBETypeLibsAPI";
2728
}
2829
}

0 commit comments

Comments
 (0)