Skip to content

Commit

Permalink
Merge pull request #983 from stakx/setup-verification
Browse files Browse the repository at this point in the history
Make verification API more generic than `Verify()`, `VerifyAll()`
  • Loading branch information
stakx committed Mar 28, 2020
2 parents 9fab772 + 6b3bf34 commit 634daa3
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 107 deletions.
10 changes: 0 additions & 10 deletions src/Moq/AutoImplementedPropertyGetterSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,5 @@ public override bool TryGetReturnValue(out object returnValue)
returnValue = this.getter.Invoke();
return true;
}

public override MockException TryVerify()
{
return this.TryVerifyInnerMock(innerMock => innerMock.TryVerify());
}

public override MockException TryVerifyAll()
{
return this.TryVerifyInnerMock(innerMock => innerMock.TryVerifyAll());
}
}
}
2 changes: 0 additions & 2 deletions src/Moq/AutoImplementedPropertySetterSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,5 @@ public override void Execute(Invocation invocation)
this.setter.Invoke(invocation.Arguments[0]);
invocation.Return();
}

public override MockException TryVerifyAll() => null;
}
}
10 changes: 0 additions & 10 deletions src/Moq/InnerMockSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,6 @@ public override bool TryGetReturnValue(out object returnValue)
return true;
}

public override MockException TryVerify()
{
return this.TryVerifyInnerMock(innerMock => innerMock.TryVerify());
}

public override MockException TryVerifyAll()
{
return this.TryVerifyInnerMock(innerMock => innerMock.TryVerifyAll());
}

public override void Uninvoke()
{
if (this.ReturnsInnerMock(out var innerMock))
Expand Down
10 changes: 1 addition & 9 deletions src/Moq/Interception/InterceptionAspects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,7 @@ public static bool Handle(Invocation invocation, Mock mock)
if (matchedSetup != null)
{
matchedSetup.EvaluatedSuccessfully(invocation);

if (matchedSetup.IsVerifiable)
{
invocation.MarkAsMatchedByVerifiableSetup();
}
else
{
invocation.MarkAsMatchedBySetup();
}
invocation.MarkAsMatchedBy(matchedSetup);

matchedSetup.SetOutParameters(invocation);

Expand Down
46 changes: 10 additions & 36 deletions src/Moq/Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ internal abstract class Invocation : IInvocation
private MethodInfo methodImplementation;
private readonly Type proxyType;
private object returnValue;
private VerificationState verificationState;
private Setup matchingSetup;
private bool verified;

/// <summary>
/// Initializes a new instance of the <see cref="Invocation"/> class.
Expand Down Expand Up @@ -68,7 +69,7 @@ public MethodInfo MethodImplementation

public object ReturnValue => this.returnValue;

internal bool Verified => this.verificationState == VerificationState.Verified;
internal bool Verified => this.verified;

/// <summary>
/// Ends the invocation as if a <see langword="return"/> statement occurred.
Expand Down Expand Up @@ -107,39 +108,20 @@ public MethodInfo MethodImplementation
/// </remarks>
public abstract void Return(object value);

internal void MarkAsMatchedBySetup() // this supports the `mock.VerifyAll()` machinery
internal void MarkAsMatchedBy(Setup setup)
{
if (this.verificationState == VerificationState.Invoked)
{
this.verificationState = VerificationState.InvokedAndMatchedBySetup;
}
}
Debug.Assert(this.matchingSetup == null);

internal void MarkAsMatchedByVerifiableSetup() // this supports the `mock.Verify()` machinery
{
if (this.verificationState == VerificationState.Invoked ||
this.verificationState == VerificationState.InvokedAndMatchedBySetup)
{
this.verificationState = VerificationState.InvokedAndMatchedByVerifiableSetup;
}
this.matchingSetup = setup;
}

internal void MarkAsVerified() => this.verificationState = VerificationState.Verified;

internal void MarkAsVerifiedIfMatchedBySetup() // this supports the `mock.VerifyAll()` machinery
{
if (this.verificationState == VerificationState.InvokedAndMatchedBySetup ||
this.verificationState == VerificationState.InvokedAndMatchedByVerifiableSetup)
{
this.verificationState = VerificationState.Verified;
}
}
internal void MarkAsVerified() => this.verified = true;

internal void MarkAsVerifiedIfMatchedByVerifiableSetup() // this supports the `mock.Verify()` machinery
internal void MarkAsVerifiedIfMatchedBy(Func<Setup, bool> predicate)
{
if (this.verificationState == VerificationState.InvokedAndMatchedByVerifiableSetup)
if (this.matchingSetup != null && predicate(this.matchingSetup))
{
this.verificationState = VerificationState.Verified;
this.verified = true;
}
}

Expand Down Expand Up @@ -189,13 +171,5 @@ public override string ToString()

return builder.ToString();
}

private enum VerificationState : byte
{
Invoked = 0,
InvokedAndMatchedBySetup,
InvokedAndMatchedByVerifiableSetup,
Verified,
}
}
}
5 changes: 3 additions & 2 deletions src/Moq/MethodCall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,9 +331,10 @@ public void SetThrowExceptionResponse(Exception exception)
this.returnOrThrowResponse = new ThrowExceptionResponse(exception);
}

public override MockException TryVerifyAll()
protected override bool TryVerifySelf(out MockException error)
{
return (this.flags & Flags.Invoked) == 0 ? MockException.UnmatchedSetup(this) : null;
error = (this.flags & Flags.Invoked) != 0 ? null : MockException.UnmatchedSetup(this);
return error == null;
}

public override void Uninvoke()
Expand Down
40 changes: 13 additions & 27 deletions src/Moq/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,7 @@ public DefaultValue DefaultValue
/// </example>
public void Verify()
{
var error = this.TryVerify();
if (error?.IsVerificationError == true)
{
throw error;
}
this.Verify(setup => setup.IsVerifiable);
}

/// <summary>
Expand All @@ -289,55 +285,45 @@ public void Verify()
/// </example>
public void VerifyAll()
{
var error = this.TryVerifyAll();
if (error?.IsVerificationError == true)
{
throw error;
}
this.Verify(setup => true);
}

internal MockException TryVerify()
private void Verify(Func<Setup, bool> predicate)
{
foreach (Invocation invocation in this.MutableInvocations)
if (!this.TryVerify(predicate, out var error) && error.IsVerificationError)
{
invocation.MarkAsVerifiedIfMatchedByVerifiableSetup();
throw error;
}

return this.TryVerifySetups(setup => setup.TryVerify());
}

internal MockException TryVerifyAll()
internal bool TryVerify(Func<Setup, bool> predicate, out MockException error)
{
foreach (Invocation invocation in this.MutableInvocations)
{
invocation.MarkAsVerifiedIfMatchedBySetup();
invocation.MarkAsVerifiedIfMatchedBy(predicate);
}

return this.TryVerifySetups(setup => setup.TryVerifyAll());
}

private MockException TryVerifySetups(Func<Setup, MockException> verifySetup)
{
var errors = new List<MockException>();

foreach (var setup in this.Setups.ToArrayLive(_ => true))
{
var error = verifySetup(setup);
if (error?.IsVerificationError == true)
if (!setup.TryVerify(predicate, out var e) && e.IsVerificationError)
{
errors.Add(error);
errors.Add(e);
}
}

if (errors.Count > 0)
{
return MockException.Combined(
error = MockException.Combined(
errors,
preamble: string.Format(CultureInfo.CurrentCulture, Resources.VerificationErrorsOfMock, this));
return false;
}
else
{
return null;
error = null;
return true;
}
}

Expand Down
5 changes: 3 additions & 2 deletions src/Moq/SequenceSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ public override void Execute(Invocation invocation)
}
}

public override MockException TryVerifyAll()
protected override bool TryVerifySelf(out MockException error)
{
return this.invoked ? null : MockException.UnmatchedSetup(this);
error = this.invoked ? null : MockException.UnmatchedSetup(this);
return error == null;
}

public override void Uninvoke()
Expand Down
72 changes: 63 additions & 9 deletions src/Moq/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,79 @@ public override string ToString()
return builder.ToString();
}

public virtual MockException TryVerify()
/// <summary>
/// Verifies this setup and/or those of its inner mock (if present and known).
/// </summary>
/// <param name="predicate">
/// Specifies which setups should be verified.
/// </param>
/// <param name="error">
/// If this setup and/or any of its inner mock (if present and known) failed verification,
/// this <see langword="out"/> parameter will receive a <see cref="MockException"/> describing the verification error(s).
/// </param>
/// <returns>
/// <see langword="true"/> if verification succeeded without any errors;
/// otherwise, <see langword="false"/>.
/// </returns>
public bool TryVerify(Func<Setup, bool> predicate, out MockException error)
{
return this.IsVerifiable ? this.TryVerifyAll() : null;
}
if (predicate(this))
{
if (!this.TryVerifySelf(out var e) && e.IsVerificationError)
{
error = e;
return false;
}
}

public abstract MockException TryVerifyAll();
return this.TryVerifyInnerMock(predicate, out error);
}

public MockException TryVerifyInnerMock(Func<Mock, MockException> verify)
/// <summary>
/// Verifies all setups of this setup's inner mock (if present and known).
/// Multiple verification errors are aggregated into a single <see cref="MockException"/>.
/// </summary>
/// <param name="predicate">
/// Specifies which setups should be verified.
/// </param>
/// <param name="error">
/// If one or more setups of this setup's inner mock (if present and known) failed verification,
/// this <see langword="out"/> parameter will receive a <see cref="MockException"/> describing the verification error(s).
/// </param>
/// <returns>
/// <see langword="true"/> if verification succeeded without any errors;
/// otherwise, <see langword="false"/>.
/// </returns>
protected virtual bool TryVerifyInnerMock(Func<Setup, bool> predicate, out MockException error)
{
if (this.ReturnsInnerMock(out var innerMock))
{
var error = verify(innerMock);
if (error?.IsVerificationError == true)
if (!innerMock.TryVerify(predicate, out var e) && e.IsVerificationError)
{
return MockException.FromInnerMockOf(this, error);
error = MockException.FromInnerMockOf(this, e);
return false;
}
}

return null;
error = null;
return true;
}

/// <summary>
/// Verifies only this setup, excluding those of its inner mock (if present and known).
/// </summary>
/// <param name="error">
/// If this setup failed verification,
/// this <see langword="out"/> parameter will receive a <see cref="MockException"/> describing the verification error.
/// </param>
/// <returns>
/// <see langword="true"/> if verification succeeded without any errors;
/// otherwise, <see langword="false"/>.
/// </returns>
protected virtual bool TryVerifySelf(out MockException error)
{
error = null;
return true;
}

public virtual void Uninvoke()
Expand Down
17 changes: 17 additions & 0 deletions tests/Moq.Tests/VerifyFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1486,6 +1486,23 @@ public void Verify_on_non_overridable_method_throws_NotSupportedException()
mock.Verify(m => m.InvokePopulate(ref It.Ref<ChildDto>.IsAny), Times.Never));
}

[Fact]
public void Verification_marks_invocations_of_inner_mocks_as_verified()
{
var mock = new Mock<IFoo>() { DefaultValue = DefaultValue.Mock };
mock.Setup(m => m.Value).Returns(1);
mock.Setup(m => m.Bar.Value).Returns(2);

// Invoke everything that has been set up, and verify everything:
_ = mock.Object.Value;
_ = mock.Object.Bar.Value;
mock.VerifyAll();

// The above call to `VerifyAll` should have marked all invocations as verified,
// including those on the inner `Bar` mock:
Mock.Get(mock.Object.Bar).VerifyNoOtherCalls();
}

public class Exclusion_of_unreachable_inner_mocks
{
[Fact]
Expand Down

0 comments on commit 634daa3

Please sign in to comment.