Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make internal verification API more generic than Verify(), VerifyAll() #983

Merged
merged 4 commits into from
Mar 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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