-
-
Notifications
You must be signed in to change notification settings - Fork 809
/
Copy pathMockException.cs
266 lines (229 loc) · 10.3 KB
/
MockException.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Security;
using System.Text;
using Moq.Async;
using Moq.Language;
using Moq.Properties;
namespace Moq
{
/// <summary>
/// Exception thrown by mocks when they are not properly set up,
/// when setups are not matched, when verification fails, etc.
/// </summary>
/// <remarks>
/// A distinct exception type is provided so that exceptions
/// thrown by a mock can be distinguished from other exceptions
/// that might be thrown in tests.
/// <para>
/// Moq does not provide a richer hierarchy of exception types, as
/// tests typically should <em>not</em> catch or expect exceptions
/// from mocks. These are typically the result of changes
/// in the tested class or its collaborators' implementation, and
/// result in fixes in the mock setup so that they disappear and
/// allow the test to pass.
/// </para>
/// </remarks>
[Serializable]
public class MockException : Exception
{
/// <summary>
/// Returns the exception to be thrown when a setup has been invoked an incorrect (unexpected) number of times.
/// </summary>
internal static MockException IncorrectNumberOfCalls(MethodCall setup, Times times, int invocationCount)
{
var message = new StringBuilder();
message.AppendLine(setup.FailMessage ?? "")
.Append(times.GetExceptionMessage(invocationCount))
.AppendLine(setup.Expression.ToStringFixed());
return new MockException(MockExceptionReasons.IncorrectNumberOfCalls, message.ToString());
}
/// <summary>
/// Returns the exception to be thrown when <see cref="Mock.Verify"/> finds no invocations (or the wrong number of invocations) that match the specified expectation.
/// </summary>
internal static MockException NoMatchingCalls(
Mock rootMock,
LambdaExpression expression,
string failMessage,
Times times,
int callCount)
{
var message = new StringBuilder();
message.AppendLine(failMessage ?? "")
.Append(times.GetExceptionMessage(callCount))
.AppendLine(expression.PartialMatcherAwareEval().ToStringFixed())
.AppendLine()
.AppendLine(Resources.PerformedInvocations)
.AppendLine();
var visitedMocks = new HashSet<Mock>();
var mocks = new Queue<Mock>();
mocks.Enqueue(rootMock);
while (mocks.Any())
{
var mock = mocks.Dequeue();
if (visitedMocks.Contains(mock)) continue;
visitedMocks.Add(mock);
message.AppendLine(mock == rootMock ? $" {mock} ({expression.Parameters[0].Name}):"
: $" {mock}:");
var invocations = mock.MutableInvocations.ToArray();
if (invocations.Any())
{
message.AppendLine();
foreach (var invocation in invocations)
{
message.Append($" {invocation}");
if (invocation.Method.ReturnType != typeof(void) && Awaitable.TryGetResultRecursive(invocation.ReturnValue) is IMocked mocked)
{
var innerMock = mocked.Mock;
mocks.Enqueue(innerMock);
message.Append($" => {innerMock}");
}
message.AppendLine();
}
}
else
{
message.AppendLine($" {Resources.NoInvocationsPerformed}");
}
message.AppendLine();
}
return new MockException(MockExceptionReasons.NoMatchingCalls, message.TrimEnd().AppendLine().ToString());
}
/// <summary>
/// Returns the exception to be thrown when a strict mock has no setup corresponding to the specified invocation.
/// </summary>
internal static MockException NoSetup(Invocation invocation)
{
return new MockException(
MockExceptionReasons.NoSetup,
string.Format(
CultureInfo.CurrentCulture,
Resources.MockExceptionMessage,
invocation.ToString(),
MockBehavior.Strict,
Resources.NoSetup));
}
/// <summary>
/// Returns the exception to be thrown when a strict mock has no setup that provides a return value for the specified invocation.
/// </summary>
internal static MockException ReturnValueRequired(Invocation invocation)
{
return new MockException(
MockExceptionReasons.ReturnValueRequired,
string.Format(
CultureInfo.CurrentCulture,
Resources.MockExceptionMessage,
invocation.ToString(),
MockBehavior.Strict,
Resources.ReturnValueRequired));
}
/// <summary>
/// Returns the exception to be thrown when a setup was not matched.
/// </summary>
internal static MockException UnmatchedSetup(Setup setup)
{
return new MockException(
MockExceptionReasons.UnmatchedSetup,
string.Format(
CultureInfo.CurrentCulture,
Resources.UnmatchedSetup,
setup));
}
internal static MockException FromInnerMockOf(ISetup setup, MockException error)
{
var message = new StringBuilder();
message.AppendLine(string.Format(CultureInfo.CurrentCulture, Resources.VerificationErrorsOfInnerMock, setup)).TrimEnd().AppendLine()
.AppendLine();
message.AppendIndented(error.Message, count: 3);
return new MockException(error.Reasons, message.ToString());
}
/// <summary>
/// Returns an exception whose message is the concatenation of the given <paramref name="errors"/>' messages
/// and whose reason(s) is the combination of the given <paramref name="errors"/>' reason(s).
/// Used by <see cref="MockFactory.VerifyMocks(Action{Mock})"/> when it finds one or more mocks with verification errors.
/// </summary>
internal static MockException Combined(IEnumerable<MockException> errors, string preamble)
{
Debug.Assert(errors != null);
Debug.Assert(errors.Any());
var reasons = default(MockExceptionReasons);
var message = new StringBuilder();
if (preamble != null)
{
message.Append(preamble).TrimEnd().AppendLine()
.AppendLine();
}
foreach (var error in errors)
{
reasons |= error.Reasons;
message.AppendIndented(error.Message, count: 3).TrimEnd().AppendLine()
.AppendLine();
}
return new MockException(reasons, message.TrimEnd().ToString());
}
/// <summary>
/// Returns the exception to be thrown when <see cref="Mock.VerifyNoOtherCalls(Mock)"/> finds invocations that have not been verified.
/// </summary>
internal static MockException UnverifiedInvocations(Mock mock, IEnumerable<Invocation> invocations)
{
var message = new StringBuilder();
message.AppendLine(string.Format(CultureInfo.CurrentCulture, Resources.UnverifiedInvocations, mock)).TrimEnd().AppendLine()
.AppendLine();
foreach (var invocation in invocations)
{
message.AppendIndented(invocation.ToString(), count: 3).TrimEnd().AppendLine();
}
return new MockException(MockExceptionReasons.UnverifiedInvocations, message.TrimEnd().ToString());
}
readonly MockExceptionReasons reasons;
MockException(MockExceptionReasons reasons, string message)
: base(message)
{
this.reasons = reasons;
}
internal MockExceptionReasons Reasons => this.reasons;
/// <summary>
/// Indicates whether this exception is a verification fault raised by Verify()
/// </summary>
public bool IsVerificationError
{
get
{
const MockExceptionReasons verificationErrorMask = MockExceptionReasons.NoMatchingCalls
| MockExceptionReasons.UnmatchedSetup
| MockExceptionReasons.UnverifiedInvocations;
return (this.reasons & verificationErrorMask) != 0;
}
}
/// <summary>
/// Supports the serialization infrastructure.
/// </summary>
/// <param name="info">Serialization information.</param>
/// <param name="context">Streaming context.</param>
protected MockException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
this.reasons = (MockExceptionReasons)info.GetValue(nameof(this.reasons), typeof(MockExceptionReasons));
}
/// <summary>
/// Supports the serialization infrastructure.
/// </summary>
/// <param name="info">Serialization information.</param>
/// <param name="context">Streaming context.</param>
[SecurityCritical]
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
base.GetObjectData(info, context);
info.AddValue(nameof(this.reasons), this.reasons);
}
}
}