Skip to content

Commit 76bc013

Browse files
authored
Merge pull request #854 from stakx/issue-733
Let `mock.Invocations.Clear()` remove traces of earlier invocations more thoroughly
2 parents 86bab00 + 32e021b commit 76bc013

File tree

7 files changed

+71
-2
lines changed

7 files changed

+71
-2
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
1111

1212
* Improved error message that is supplied with `ArgumentException` thrown when `Setup` or `Verify` are called on a protected method if the method could not be found with both the name and compatible argument types specified (@thomasfdm, #852).
1313

14+
* `mock.Invocations.Clear()` now removes traces of previous invocations more thoroughly by additionally resetting all setups to an "unmatched" state. (@stakx, #854)
15+
1416
* Consistent `Callback` delegate validation regardless of whether or not `Callback` is preceded by a `Returns`: Validation for post-`Returns` callback delegates used to be very relaxed, but is now equally strict as in the pre-`Returns` case.) (@stakx, #876)
1517

1618
#### Added
@@ -19,7 +21,10 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1
1921

2022
#### Fixed
2123

24+
* `Invocations.Clear()` does not cause `Verify` to fail (@jchessir, #733)
25+
2226
* Regression: `SetupAllProperties` can no longer set up properties whose names start with `Item`. (@mattzink, #870; @kaan-kaya, #869)
27+
2328
* Regression: `MockDefaultValueProvider` will no longer attempt to set `CallBase` to true for mocks generated for delegates. (@dammejed, #874)
2429

2530

src/Moq/InvocationCollection.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System;
55
using System.Collections;
66
using System.Collections.Generic;
7-
using System.Linq;
7+
using System.Diagnostics;
88

99
namespace Moq
1010
{
@@ -16,6 +16,14 @@ internal sealed class InvocationCollection : IInvocationList
1616
private int count = 0;
1717

1818
private readonly object invocationsLock = new object();
19+
private readonly Mock owner;
20+
21+
public InvocationCollection(Mock owner)
22+
{
23+
Debug.Assert(owner != null);
24+
25+
this.owner = owner;
26+
}
1927

2028
public int Count
2129
{
@@ -68,6 +76,9 @@ public void Clear()
6876
this.invocations = null;
6977
this.count = 0;
7078
this.capacity = 0;
79+
80+
this.owner.Setups.UninvokeAll();
81+
// ^ TODO: Currently this could cause a deadlock as another lock will be taken inside this one!
7182
}
7283
}
7384

src/Moq/MethodCall.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,12 @@ public override MockException TryVerifyAll()
329329
return (this.flags & Flags.Invoked) == 0 ? MockException.UnmatchedSetup(this) : null;
330330
}
331331

332+
public override void Uninvoke()
333+
{
334+
this.flags &= ~Flags.Invoked;
335+
this.limitInvocationCountResponse?.Reset();
336+
}
337+
332338
public void Verifiable()
333339
{
334340
this.flags |= Flags.Verifiable;
@@ -388,6 +394,11 @@ public LimitInvocationCountResponse(MethodCall setup, int maxCount)
388394
this.count = 0;
389395
}
390396

397+
public void Reset()
398+
{
399+
this.count = 0;
400+
}
401+
391402
public void RespondTo(Invocation invocation)
392403
{
393404
++this.count;

src/Moq/Mock.Generic.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ public Mock(MockBehavior behavior, params object[] args)
139139
this.constructorArguments = args;
140140
this.defaultValueProvider = DefaultValueProvider.Empty;
141141
this.eventHandlers = new EventHandlerCollection();
142-
this.invocations = new InvocationCollection();
142+
this.invocations = new InvocationCollection(this);
143143
this.name = CreateUniqueDefaultMockName();
144144
this.setups = new SetupCollection();
145145
this.switches = Switches.Default;

src/Moq/Setup.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,9 @@ public MockException TryVerifyInnerMock(Func<Mock, MockException> verify)
109109

110110
return null;
111111
}
112+
113+
public virtual void Uninvoke()
114+
{
115+
}
112116
}
113117
}

src/Moq/SetupCollection.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ public IEnumerable<Setup> GetInnerMockSetups()
119119
return this.ToArrayLive(setup => setup.ReturnsInnerMock(out _));
120120
}
121121

122+
public void UninvokeAll()
123+
{
124+
lock (this.setups)
125+
{
126+
foreach (var setup in this.setups)
127+
{
128+
setup.Uninvoke();
129+
}
130+
}
131+
}
132+
122133
public Setup[] ToArrayLive(Func<Setup, bool> predicate)
123134
{
124135
var matchingSetups = new Stack<Setup>();

tests/Moq.Tests/InvocationsFixture.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,32 @@ public void Invocations_for_object_methods_on_interface_proxy_record_return_valu
128128
var invocation = mock.MutableInvocations.ToArray()[0];
129129
Assert.Equal(42, invocation.ReturnValue);
130130
}
131+
132+
[Fact]
133+
public void Invocations_Clear_also_resets_setup_verification_state()
134+
{
135+
var mock = new Mock<IComparable>();
136+
mock.Setup(m => m.CompareTo(default));
137+
_ = mock.Object.CompareTo(default);
138+
mock.VerifyAll(); // ensure setup has been matched
139+
140+
mock.Invocations.Clear();
141+
var ex = Assert.Throws<MockException>(() => mock.VerifyAll());
142+
Assert.Equal(MockExceptionReasons.UnmatchedSetup, ex.Reasons);
143+
}
144+
145+
[Fact]
146+
[Obsolete()]
147+
public void Invocations_Clear_resets_count_kept_by_setup_AtMost()
148+
{
149+
var mock = new Mock<IComparable>();
150+
mock.Setup(m => m.CompareTo(default)).Returns(0).AtMostOnce();
151+
_ = mock.Object.CompareTo(default);
152+
153+
mock.Invocations.Clear();
154+
_ = mock.Object.CompareTo(default); // this second call should now count as the first
155+
var ex = Assert.Throws<MockException>(() => mock.Object.CompareTo(default));
156+
Assert.Equal(MockExceptionReasons.MoreThanOneCall, ex.Reasons);
157+
}
131158
}
132159
}

0 commit comments

Comments
 (0)