diff --git a/changelog.md b/changelog.md index b9c73981b..ea1016e80 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,8 @@ - FileLoadException for 'System.Threading.Tasks.Extensions, Version=4.2.0.1' (or similar) at `Moq.Async.AwaitableFactory..cctor()` on .NET Framework 4.6.2+ no longer occurs when updating Moq and other dependencies bring in newer versions of the package. Common non-ValueTask usage succeeds without binding redirects. The initialization now only eagerly registers Task providers and uses runtime FullName checks + deferred creation for ValueTask support. [#1648](https://github.com/devlooped/moq/issues/1648) +- `Verify`/`VerifyAll` now correctly fail if a `MockSequence` step was set up but never invoked. Previously, sequence setups were silently excluded from verification (the same exemption used for `mock.When(...)` conditions), so a test could omit calls to later sequence steps and still pass `VerifyAll()`. Note: this may turn previously-passing tests that rely on this gap into failing ones — that's the fix working as intended. [#1683](https://github.com/devlooped/moq/issues/1683) + ## [v4.20.72](https://github.com/devlooped/moq/tree/v4.20.72) (2024-09-07) [Full Changelog](https://github.com/devlooped/moq/compare/v4.20.71...v4.20.72) diff --git a/src/Moq.Tests/MockSequenceFixture.cs b/src/Moq.Tests/MockSequenceFixture.cs index eab722405..97efe61c6 100644 --- a/src/Moq.Tests/MockSequenceFixture.cs +++ b/src/Moq.Tests/MockSequenceFixture.cs @@ -1,6 +1,8 @@ // 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 Xunit; namespace Moq.Tests @@ -114,6 +116,22 @@ public void SameMockInvalidSequenceFail() Assert.Throws(() => a.Object.Do(200)); } + + [Fact] + public void SameMockNotAllSequenceCalledFail() + { + var a = new Mock(MockBehavior.Strict); + + var sequence = new MockSequence(); + a.InSequence(sequence).Setup(x => x.Do(100)).Returns(101); + a.InSequence(sequence).Setup(x => x.Do(200)).Returns(201); + a.InSequence(sequence).Setup(x => x.Do(300)).Returns(301); + + a.Object.Do(100); + a.Object.Do(200); + + Assert.Throws(() => a.VerifyAll()); + } public interface IFoo { diff --git a/src/Moq/Condition.cs b/src/Moq/Condition.cs index c245974a8..036d254f2 100644 --- a/src/Moq/Condition.cs +++ b/src/Moq/Condition.cs @@ -10,14 +10,17 @@ sealed class Condition Func condition; Action? success; - public Condition(Func condition, Action? success = null) + public Condition(Func condition, Action? success = null, bool sequence = false) { this.condition = condition; this.success = success; + this.sequence = sequence; } public bool IsTrue => this.condition.Invoke(); + public bool sequence { get; } + public void SetupEvaluatedSuccessfully() => this.success?.Invoke(); } } diff --git a/src/Moq/Mock.cs b/src/Moq/Mock.cs index b178f0475..9448cdb63 100644 --- a/src/Moq/Mock.cs +++ b/src/Moq/Mock.cs @@ -293,7 +293,7 @@ internal void Verify(Func predicate, HashSet verifiedMocks) var errors = new List(); - foreach (var setup in this.MutableSetups.FindAll(setup => !setup.IsConditional && predicate(setup))) + foreach (var setup in this.MutableSetups.FindAll(setup => (!setup.IsConditional || setup.Condition!.sequence) && predicate(setup))) { try { diff --git a/src/Moq/MockSequence.cs b/src/Moq/MockSequence.cs index b26ca5eae..d83605320 100644 --- a/src/Moq/MockSequence.cs +++ b/src/Moq/MockSequence.cs @@ -44,7 +44,8 @@ internal ISetupConditionResult For(Mock mock) return new WhenPhrase(mock, new Condition( condition: () => expectationPosition == sequenceStep, - success: NextStep)); + success: NextStep, + sequence: true)); } }