diff --git a/src/Moq/Times.cs b/src/Moq/Times.cs index 98a4b1ad8..7ea1db280 100644 --- a/src/Moq/Times.cs +++ b/src/Moq/Times.cs @@ -2,6 +2,7 @@ // All rights reserved. Licensed under the BSD 3-Clause License; see License.txt. using System; +using System.Diagnostics; using System.Globalization; using Moq.Properties; @@ -22,6 +23,28 @@ private Times(Kind kind, int from, int to) this.kind = kind; } + private void Deconstruct(out int from, out int to) + { + if (this.kind == default) + { + // This branch makes `default(Times)` equivalent to `Times.AtLeastOnce()`, + // which is the implicit default across Moq's API for overloads that don't + // accept a `Times` instance. While user code shouldn't use `default(Times)` + // (but instead either specify `Times` explicitly or not at all), it is + // easy enough to correct: + + Debug.Assert(this.kind == Kind.AtLeastOnce); + + from = 1; + to = int.MaxValue; + } + else + { + from = this.from; + to = this.to; + } + } + /// public static Times AtLeast(int callCount) { @@ -115,7 +138,9 @@ public static Times Once() /// public bool Equals(Times other) { - return this.from == other.from && this.to == other.to; + var (from, to) = this; + var (otherFrom, otherTo) = other; + return from == otherFrom && to == otherTo; } /// @@ -127,7 +152,8 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - return this.from.GetHashCode() ^ this.to.GetHashCode(); + var (from, to) = this; + return from.GetHashCode() ^ to.GetHashCode(); } /// @@ -144,8 +170,13 @@ public override int GetHashCode() internal string GetExceptionMessage(int callCount) { - var from = this.kind == Kind.BetweenExclusive ? this.from - 1 : this.from; - var to = this.kind == Kind.BetweenExclusive ? this.to + 1 : this.to; + var (from, to) = this; + + if (this.kind == Kind.BetweenExclusive) + { + --from; + ++to; + } string message = null; switch (this.kind) @@ -166,13 +197,14 @@ internal string GetExceptionMessage(int callCount) internal bool Verify(int callCount) { - return this.from <= callCount && callCount <= this.to; + var (from, to) = this; + return from <= callCount && callCount <= to; } private enum Kind { - AtLeast, AtLeastOnce, + AtLeast, AtMost, AtMostOnce, BetweenExclusive, diff --git a/tests/Moq.Tests/TimesFixture.cs b/tests/Moq.Tests/TimesFixture.cs index c496f6a0e..d67f4d669 100644 --- a/tests/Moq.Tests/TimesFixture.cs +++ b/tests/Moq.Tests/TimesFixture.cs @@ -9,6 +9,15 @@ namespace Moq.Tests { public class TimesFixture { + [Theory] + [InlineData(0, false)] + [InlineData(1, true)] + [InlineData(int.MaxValue, true)] + public void default_ranges_between_one_and_MaxValue(int count, bool verifies) + { + Assert.Equal(verifies, default(Times).Verify(count)); + } + [Fact] public void AtLeastOnceRangesBetweenOneAndMaxValue() { @@ -174,6 +183,20 @@ public void OnceChecksOneTime() public class Equality { +#pragma warning disable xUnit2000 // Constants and literals should be the expected argument + [Fact] + public void default_Equals_AtLeastOnce() + { + Assert.Equal(Times.AtLeastOnce(), default(Times)); + } +#pragma warning restore xUnit2000 + + [Fact] + public void default_GetHashCode_equals_AtLeastOnce_GetHashCode() + { + Assert.Equal(Times.AtLeastOnce().GetHashCode(), default(Times).GetHashCode()); + } + [Fact] public void AtMostOnce_equals_Between_0_1_inclusive() {