Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.

Commit 114018d

Browse files
committed
Add new ways of verifying mocks, with syntax and fluent APIs
Just like for Arg, Raise and Setup, there is now a Syntax-based Verify which allows much more succinct code. With the following using at the top: ``` using static Moq.Syntax; ``` You can now perform verifications on a mock as follows: ``` // Verify all "verifiable" calls, i.e. those with a Once/Never/etc. setup. Verify(calculator); // Verify a specific invocation was made at least once Verify(calculator).Add(2, 3); // Verify it was never called Verify(calculator).Add(1, 1).Never(); // Verify it was called exactly once Verify(calculator).Add(1, 1).Once(); ``` Verifiable setups can now just add `Once`, `Never` and `Exactly` directly on the setup too (unlike v4 which only had `Verifiable()` which meant `AtLeastOnce`) ``` var calculator = Mock.Of<Calculator>(); // The given call is expected only once calculator.Add(2, 3).Returns(5).Once(); // The given call is never expected calculator.Add(-1, -1).Never(); // The given call is expected at least once. Equivalent to v4 Verifiable calculator.Add(1, 1).AtLeastOnce(); // A single call to Verify will verify all the above Verify(calculator); // Also supported, non-syntax version Verify.Called(calculator); // And instance version (via extension method backwards compatible) calculator.Verify(); ``` A non-syntax version is also provided, which is basically called from the syntax one: ``` // Verify all "verifiable" calls, i.e. those with a Once/Never/etc. setup. Verify.Called(calculator); // Verify a specific invocation was made at least once Verify.Called(calculator).Add(2, 3); // Verify it was never called Verify.Called(calculator).Add(1, 1).Never(); // Or alternative syntax depending on style preference Verify.NotCalled(calculator).Add(2, 3); ``` A mechanism in the SDK for cloning mocks was added, so that they can be "frozen" and tweaked independently from the original mock (i.e. for verification purposes). As part of cleaning up a mock's behaviors for cloning and verification, we took the chance to split the former `MockTrackingBehavior` (which was doing two things) into `MockContextBehavior` (the one that sets the `MockContext` static properties) and `MockRecordingBehavior` (which does the actual invocation recording). This allows getting a cloned mock that does not record calls, for example, for verifying purposes or diagnostics, say. This could be used to also clone on debugger inspection, to allow preserving the actual calls instead of having the inspection to modify the actual calls (which is what happens typically by default in all mocking libraries).
1 parent 58c1300 commit 114018d

35 files changed

+1488
-140
lines changed

src/Moq/Moq.Sdk.Tests/DefaultMockTests.cs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,35 @@ public void ThrowsIfNullStunt()
1414
=> Assert.Throws<ArgumentNullException>(() => new DefaultMock(null));
1515

1616
[Fact]
17-
public void AddsMockTrackingBehavior()
17+
public void AddsMockContextBehavior()
1818
{
1919
var mock = new DefaultMock(new FakeStunt());
2020

21-
Assert.Collection(mock.Behaviors, x => Assert.IsType<MockTrackingBehavior>(x));
21+
Assert.Contains(mock.Behaviors, x => x is MockContextBehavior);
2222
}
2323

2424
[Fact]
25-
public void PreventsDuplicateMockTrackingBehavior()
25+
public void AddsMockRecordingBehavior()
2626
{
2727
var mock = new DefaultMock(new FakeStunt());
2828

29-
Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockTrackingBehavior()));
29+
Assert.Contains(mock.Behaviors, x => x is MockRecordingBehavior);
30+
}
31+
32+
[Fact]
33+
public void PreventsDuplicateMockContextBehavior()
34+
{
35+
var mock = new DefaultMock(new FakeStunt());
36+
37+
Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockContextBehavior()));
38+
}
39+
40+
[Fact]
41+
public void PreventsDuplicateMockRecordingBehavior()
42+
{
43+
var mock = new DefaultMock(new FakeStunt());
44+
45+
Assert.Throws<InvalidOperationException>(() => mock.Behaviors.Add(new MockRecordingBehavior()));
3046
}
3147

3248
[Fact]
@@ -40,33 +56,38 @@ public void TrackMockBehaviors()
4056
new MethodInvocation(stunt, typeof(FakeStunt).GetMethod("Do")),
4157
Array.Empty<IArgumentMatcher>());
4258

59+
var initialBehaviors = stunt.Behaviors.Count;
4360
var behavior = new MockBehaviorPipeline(setup);
4461

4562
stunt.AddBehavior(behavior);
4663
stunt.AddBehavior(new DelegateStuntBehavior((m, n) => n().Invoke(m, n)));
47-
Assert.Equal(3, stunt.Behaviors.Count);
64+
Assert.Equal(initialBehaviors + 2, stunt.Behaviors.Count);
4865

4966
Assert.Single(stunt.Mock.Setups);
5067
Assert.Same(behavior, stunt.Mock.GetPipeline(setup));
5168

5269
stunt.Behaviors.Remove(behavior);
5370

54-
Assert.Equal(2, stunt.Behaviors.Count);
71+
Assert.Equal(initialBehaviors + 1, stunt.Behaviors.Count);
5572
Assert.Empty(stunt.Mock.Setups);
5673
}
5774

5875
[Fact]
5976
public void AddPipelineForSetupIfMissing()
6077
{
6178
var stunt = new FakeStunt();
79+
// Forces initialization of the default mock.
80+
Assert.NotNull(stunt.Mock);
81+
82+
var initialBehaviors = stunt.Behaviors.Count;
6283
var setup = new MockSetup(
6384
new MethodInvocation(stunt, typeof(FakeStunt).GetMethod("Do")),
6485
Array.Empty<IArgumentMatcher>());
6586

6687
var behavior = stunt.Mock.GetPipeline(setup);
6788

6889
Assert.NotNull(behavior);
69-
Assert.Equal(2, stunt.Behaviors.Count);
90+
Assert.Equal(initialBehaviors + 1, stunt.Behaviors.Count);
7091
Assert.Single(stunt.Mock.Setups);
7192
}
7293

src/Moq/Moq.Sdk.Tests/Fakes.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public class FakeSetup : IMockSetup
2828

2929
public IArgumentMatcher[] Matchers { get; set; } = new IArgumentMatcher[0];
3030

31+
public Times? Occurrence { get; set; }
32+
33+
public StateBag State { get; } = new StateBag();
34+
3135
IMethodInvocation IMockSetup.Invocation => Invocation;
3236

3337
public bool Equals(IMockSetup other) => base.Equals(other);

src/Moq/Moq.Sdk.Tests/MockBehaviorTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void ExecutesAnonymousBehavior()
3434
[Fact]
3535
public void RecordsInvocation()
3636
{
37-
var behavior = new MockTrackingBehavior();
37+
var behavior = new MockRecordingBehavior();
3838
var mock = new Mocked();
3939

4040
behavior.Execute(new MethodInvocation(mock, typeof(object).GetMethod(nameof(object.ToString))),
@@ -46,12 +46,12 @@ public void RecordsInvocation()
4646
[Fact]
4747
public void ThrowsForNonIMocked()
4848
{
49-
var behavior = new MockTrackingBehavior();
49+
var behavior = new MockRecordingBehavior();
5050

5151
Assert.Throws<ArgumentException>(() => behavior.Execute(new MethodInvocation(
5252
new object(),
5353
typeof(Mocked).GetProperty(nameof(IMocked.Mock)).GetGetMethod()),
54-
null));
54+
() => (m, n) => m.CreateValueReturn(null)));
5555
}
5656

5757
[Fact]

src/Moq/Moq.Sdk.Tests/MockTrackingBehaviorTests.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public void SetsCurrentInvocationAndSetup()
1313
{
1414
var target = new TrackingMock();
1515
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
16-
var tracking = new MockTrackingBehavior();
16+
var tracking = new MockContextBehavior();
1717

1818
Assert.NotNull(tracking.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));
1919

@@ -27,9 +27,9 @@ public void RecordsInvocation()
2727
{
2828
var target = new TrackingMock();
2929
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
30-
var tracking = new MockTrackingBehavior();
30+
var recording = new MockRecordingBehavior();
3131

32-
Assert.NotNull(tracking.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));
32+
Assert.NotNull(recording.Execute(invocation, () => (m, n) => m.CreateValueReturn(null)));
3333

3434
Assert.Single(target.Mock.Invocations);
3535
}
@@ -39,7 +39,7 @@ public void SkipInvocationRecordingIfSetupScopeActive()
3939
{
4040
var target = new TrackingMock();
4141
var invocation = new MethodInvocation(target, typeof(TrackingMock).GetMethod(nameof(TrackingMock.Do)));
42-
var tracking = new MockTrackingBehavior();
42+
var tracking = new MockContextBehavior();
4343

4444
using (new SetupScope())
4545
{
Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
using System;
2+
using Xunit;
3+
4+
namespace Moq.Sdk.Tests
5+
{
6+
public class TimesFixture
7+
{
8+
[Theory]
9+
[InlineData(0, false)]
10+
[InlineData(1, true)]
11+
[InlineData(int.MaxValue, true)]
12+
public void DefaultTimesRangesBetweenOneAndMaxValue(int count, bool verifies)
13+
{
14+
Assert.Equal(verifies, default(Times).Validate(count));
15+
}
16+
17+
[Fact]
18+
public void AtLeastOnceRangesBetweenOneAndMaxValue()
19+
{
20+
var target = Times.AtLeastOnce;
21+
22+
Assert.False(target.Validate(-1));
23+
Assert.False(target.Validate(0));
24+
Assert.True(target.Validate(1));
25+
Assert.True(target.Validate(5));
26+
Assert.True(target.Validate(int.MaxValue));
27+
}
28+
29+
[Fact]
30+
public void AtLeastThrowsIfTimesLessThanOne()
31+
{
32+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtLeast(0));
33+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtLeast(-1));
34+
}
35+
36+
[Fact]
37+
public void AtLeastRangesBetweenTimesAndMaxValue()
38+
{
39+
var target = Times.AtLeast(10);
40+
41+
Assert.False(target.Validate(-1));
42+
Assert.False(target.Validate(0));
43+
Assert.False(target.Validate(9));
44+
Assert.True(target.Validate(10));
45+
Assert.True(target.Validate(int.MaxValue));
46+
}
47+
48+
[Fact]
49+
public void AtMostOnceRangesBetweenZeroAndOne()
50+
{
51+
var target = Times.AtMostOnce;
52+
53+
Assert.False(target.Validate(-1));
54+
Assert.True(target.Validate(0));
55+
Assert.True(target.Validate(1));
56+
Assert.False(target.Validate(5));
57+
Assert.False(target.Validate(int.MaxValue));
58+
}
59+
60+
[Fact]
61+
public void AtMostThrowsIfTimesLessThanZero()
62+
{
63+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtMost(-1));
64+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.AtMost(-2));
65+
}
66+
67+
[Fact]
68+
public void AtMostRangesBetweenZeroAndTimes()
69+
{
70+
var target = Times.AtMost(10);
71+
72+
Assert.False(target.Validate(-1));
73+
Assert.True(target.Validate(0));
74+
Assert.True(target.Validate(6));
75+
Assert.True(target.Validate(10));
76+
Assert.False(target.Validate(11));
77+
Assert.False(target.Validate(int.MaxValue));
78+
}
79+
80+
[Fact]
81+
public void ExactlyThrowsIfTimesLessThanZero()
82+
{
83+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.Exactly(-1));
84+
Assert.Throws<ArgumentOutOfRangeException>(() => Times.Exactly(-2));
85+
}
86+
87+
[Fact]
88+
public void ExactlyCheckExactTimes()
89+
{
90+
var target = Times.Exactly(10);
91+
92+
Assert.False(target.Validate(-1));
93+
Assert.False(target.Validate(0));
94+
Assert.False(target.Validate(9));
95+
Assert.True(target.Validate(10));
96+
Assert.False(target.Validate(11));
97+
Assert.False(target.Validate(int.MaxValue));
98+
}
99+
100+
[Fact]
101+
public void NeverChecksZeroTimes()
102+
{
103+
var target = Times.Never;
104+
105+
Assert.False(target.Validate(-1));
106+
Assert.True(target.Validate(0));
107+
Assert.False(target.Validate(1));
108+
Assert.False(target.Validate(int.MaxValue));
109+
}
110+
111+
[Fact]
112+
public void OnceChecksOneTime()
113+
{
114+
var target = Times.Once;
115+
116+
Assert.False(target.Validate(-1));
117+
Assert.False(target.Validate(0));
118+
Assert.True(target.Validate(1));
119+
Assert.False(target.Validate(int.MaxValue));
120+
}
121+
122+
public class Deconstruction
123+
{
124+
[Fact]
125+
public void AtLeast_n_deconstructs_to_n_MaxValue()
126+
{
127+
const int n = 42;
128+
129+
var (from, to) = Times.AtLeast(n);
130+
Assert.Equal(n, from);
131+
Assert.Equal(int.MaxValue, to);
132+
}
133+
134+
[Fact]
135+
public void AtLeastOnce_deconstructs_to_1_MaxValue()
136+
{
137+
var (from, to) = Times.AtLeastOnce;
138+
Assert.Equal(1, from);
139+
Assert.Equal(int.MaxValue, to);
140+
}
141+
142+
[Fact]
143+
public void AtMost_n_deconstructs_to_0_n()
144+
{
145+
const int n = 42;
146+
147+
var (from, to) = Times.AtMost(n);
148+
Assert.Equal(0, from);
149+
Assert.Equal(n, to);
150+
}
151+
152+
[Fact]
153+
public void AtMostOnce_deconstructs_to_0_1()
154+
{
155+
var (from, to) = Times.AtMostOnce;
156+
Assert.Equal(0, from);
157+
Assert.Equal(1, to);
158+
}
159+
160+
[Fact]
161+
public void Exactly_n_deconstructs_to_n_n()
162+
{
163+
const int n = 42;
164+
var (from, to) = Times.Exactly(n);
165+
Assert.Equal(n, from);
166+
Assert.Equal(n, to);
167+
}
168+
169+
[Fact]
170+
public void Once_deconstructs_to_1_1()
171+
{
172+
var (from, to) = Times.Once;
173+
Assert.Equal(1, from);
174+
Assert.Equal(1, to);
175+
}
176+
177+
[Fact]
178+
public void Never_deconstructs_to_0_0()
179+
{
180+
var (from, to) = Times.Never;
181+
Assert.Equal(0, from);
182+
Assert.Equal(0, to);
183+
}
184+
}
185+
186+
public class Equality
187+
{
188+
#pragma warning disable xUnit2000 // Constants and literals should be the expected argument
189+
[Fact]
190+
public void default_Equals_AtLeastOnce()
191+
{
192+
Assert.Equal(Times.AtLeastOnce, default(Times));
193+
}
194+
#pragma warning restore xUnit2000
195+
196+
[Fact]
197+
public void default_GetHashCode_equals_AtLeastOnce_GetHashCode()
198+
{
199+
Assert.Equal(Times.AtLeastOnce.GetHashCode(), default(Times).GetHashCode());
200+
}
201+
202+
[Fact]
203+
public void Once_equals_Once()
204+
{
205+
Assert.Equal(Times.Once, Times.Once);
206+
}
207+
208+
[Fact]
209+
public void Once_equals_Exactly_1()
210+
{
211+
Assert.Equal(Times.Once, Times.Exactly(1));
212+
}
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)