Skip to content

Commit

Permalink
Merge pull request #984 from stakx/mock-setups-1
Browse files Browse the repository at this point in the history
Expose `Mock.Setups`, part 1: Basics
  • Loading branch information
stakx authored Mar 29, 2020
2 parents c090aeb + 02e6e85 commit b1437af
Show file tree
Hide file tree
Showing 15 changed files with 300 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is loosely based on [Keep a Changelog](http://keepachangelog.com/en/1

#### Added

* A mock's setups can now be inspected via the new `Mock.Setups` collection property (@stakx, #984)
* New `.Protected().Setup` and `Protected().Verify` method overloads to deal with generic methods (@JmlSaul, #967)
* Two new public methods in `Times`: `bool Validate(int count)` and `string ToString()` (@stakx, 975)

Expand Down
2 changes: 1 addition & 1 deletion src/Moq/AsInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public override TInterface Object
get { return this.owner.Object as TInterface; }
}

internal override SetupCollection Setups => this.owner.Setups;
internal override SetupCollection MutableSetups => this.owner.MutableSetups;

public override Switches Switches
{
Expand Down
32 changes: 32 additions & 0 deletions src/Moq/ISetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.

using System;
using System.Linq.Expressions;

namespace Moq
{
/// <summary>
/// A setup configured on a mock.
/// </summary>
/// <seealso cref="Mock.Setups"/>
public interface ISetup
{
/// <summary>
/// The setup expression.
/// </summary>
LambdaExpression Expression { get; }

/// <summary>
/// Gets whether this setup is conditional.
/// </summary>
/// <seealso cref="Mock{T}.When(Func{bool})"/>
bool IsConditional { get; }

/// <summary>
/// Gets whether this setup has been overridden
/// (that is, whether it is being shadowed by a more recent non-conditional setup with an equal expression).
/// </summary>
bool IsOverridden { get; }
}
}
15 changes: 15 additions & 0 deletions src/Moq/ISetupList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD.
// All rights reserved. Licensed under the BSD 3-Clause License; see License.txt.

using System.Collections.Generic;

namespace Moq
{
/// <summary>
/// A list of setups that have been configured on a mock,
/// in chronological order (that is, oldest setup first, most recent setup last).
/// </summary>
public interface ISetupList : IReadOnlyList<ISetup>
{
}
}
2 changes: 1 addition & 1 deletion src/Moq/InnerMockSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public override void Uninvoke()
{
if (this.ReturnsInnerMock(out var innerMock))
{
innerMock.Setups.UninvokeAll();
innerMock.MutableSetups.UninvokeAll();
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/Moq/Interception/InterceptionAspects.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public static bool Handle(Invocation invocation, Mock mock)

private static bool HandleEquals(Invocation invocation, Mock mock)
{
if (IsObjectMethod(invocation.Method) && !mock.Setups.Any(c => IsObjectMethod(c.Method, "Equals")))
if (IsObjectMethod(invocation.Method) && !mock.MutableSetups.Any(c => IsObjectMethod(c.Method, "Equals")))
{
invocation.Return(ReferenceEquals(invocation.Arguments.First(), mock.Object));
return true;
Expand All @@ -48,7 +48,7 @@ private static bool HandleFinalize(Invocation invocation, Mock mock)
private static bool HandleGetHashCode(Invocation invocation, Mock mock)
{
// Only if there is no corresponding setup for `GetHashCode()`
if (IsObjectMethod(invocation.Method) && !mock.Setups.Any(c => IsObjectMethod(c.Method, "GetHashCode")))
if (IsObjectMethod(invocation.Method) && !mock.MutableSetups.Any(c => IsObjectMethod(c.Method, "GetHashCode")))
{
invocation.Return(mock.GetHashCode());
return true;
Expand All @@ -62,7 +62,7 @@ private static bool HandleGetHashCode(Invocation invocation, Mock mock)
private static bool HandleToString(Invocation invocation, Mock mock)
{
// Only if there is no corresponding setup for `ToString()`
if (IsObjectMethod(invocation.Method) && !mock.Setups.Any(c => IsObjectMethod(c.Method, "ToString")))
if (IsObjectMethod(invocation.Method) && !mock.MutableSetups.Any(c => IsObjectMethod(c.Method, "ToString")))
{
invocation.Return(mock.ToString() + ".Object");
return true;
Expand Down Expand Up @@ -100,7 +100,7 @@ internal static class FindAndExecuteMatchingSetup
{
public static bool Handle(Invocation invocation, Mock mock)
{
var matchedSetup = mock.Setups.FindMatchFor(invocation);
var matchedSetup = mock.MutableSetups.FindMatchFor(invocation);
if (matchedSetup != null)
{
matchedSetup.EvaluatedSuccessfully(invocation);
Expand Down Expand Up @@ -139,7 +139,7 @@ public static bool Handle(Invocation invocation, Mock mock)
var @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetAddMethod(true) == implementingMethod);
if (@event != null)
{
bool doesntHaveEventSetup = !mock.Setups.HasEventSetup;
bool doesntHaveEventSetup = !mock.MutableSetups.HasEventSetup;

if (mock.CallBase && !invocation.Method.IsAbstract)
{
Expand Down Expand Up @@ -167,7 +167,7 @@ public static bool Handle(Invocation invocation, Mock mock)
var @event = implementingMethod.DeclaringType.GetEvents(bindingFlags).SingleOrDefault(e => e.GetRemoveMethod(true) == implementingMethod);
if (@event != null)
{
bool doesntHaveEventSetup = !mock.Setups.HasEventSetup;
bool doesntHaveEventSetup = !mock.MutableSetups.HasEventSetup;

if (mock.CallBase && !invocation.Method.IsAbstract)
{
Expand Down Expand Up @@ -312,7 +312,7 @@ public static bool Handle(Invocation invocation, Mock mock)
{
propertyValue = CreateInitialPropertyValue(mock, getter);
getterSetup = new AutoImplementedPropertyGetterSetup(expression, getter, () => propertyValue);
mock.Setups.Add(getterSetup);
mock.MutableSetups.Add(getterSetup);
}

// If we wanted to optimise for speed, we'd probably be forgiven
Expand All @@ -335,7 +335,7 @@ public static bool Handle(Invocation invocation, Mock mock)
{
propertyValue = newValue;
});
mock.Setups.Add(setterSetup);
mock.MutableSetups.Add(setterSetup);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Moq/InvocationCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void Clear()
this.count = 0;
this.capacity = 0;

this.owner.Setups.UninvokeAll();
this.owner.MutableSetups.UninvokeAll();
// ^ TODO: Currently this could cause a deadlock as another lock will be taken inside this one!
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/Mock.Generic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ internal override Type MockedType
get { return typeof(T); }
}

internal override SetupCollection Setups => this.setups;
internal override SetupCollection MutableSetups => this.setups;

internal override Type TargetType => typeof(T);

Expand Down
30 changes: 18 additions & 12 deletions src/Moq/Mock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,13 @@ public DefaultValue DefaultValue
/// </summary>
internal abstract DefaultValueProvider AutoSetupPropertiesDefaultValueProvider { get; set; }

internal abstract SetupCollection Setups { get; }
internal abstract SetupCollection MutableSetups { get; }

/// <summary>
/// Gets the setups that have been configured on this mock,
/// in chronological order (that is, oldest setup first, most recent setup last).
/// </summary>
public ISetupList Setups => this.MutableSetups;

/// <summary>
/// A set of switches that influence how this mock will operate.
Expand Down Expand Up @@ -260,7 +266,7 @@ public DefaultValue DefaultValue
/// </example>
public void Verify()
{
this.Verify(setup => setup.IsVerifiable);
this.Verify(setup => !setup.IsOverridden && !setup.IsConditional && setup.IsVerifiable);
}

/// <summary>
Expand All @@ -285,7 +291,7 @@ public void Verify()
/// </example>
public void VerifyAll()
{
this.Verify(setup => true);
this.Verify(setup => !setup.IsOverridden && !setup.IsConditional);
}

private void Verify(Func<Setup, bool> predicate)
Expand All @@ -305,7 +311,7 @@ internal bool TryVerify(Func<Setup, bool> predicate, out MockException error)

var errors = new List<MockException>();

foreach (var setup in this.Setups.ToArrayLive(_ => true))
foreach (var setup in this.MutableSetups.ToArray(predicate))
{
if (!setup.TryVerify(predicate, out var e) && e.IsVerificationError)
{
Expand Down Expand Up @@ -395,7 +401,7 @@ private static void VerifyNoOtherCalls(Mock mock, HashSet<Mock> verifiedMocks)

var unverifiedInvocations = mock.MutableInvocations.ToArray(invocation => !invocation.Verified);

var innerMockSetups = mock.Setups.GetInnerMockSetups();
var innerMockSetups = mock.MutableSetups.GetInnerMockSetups();

if (unverifiedInvocations.Any())
{
Expand Down Expand Up @@ -514,7 +520,7 @@ internal static MethodCall Setup(Mock mock, LambdaExpression expression, Conditi
return Mock.SetupRecursive(mock, expression, setupLast: (part, targetMock) =>
{
var setup = new MethodCall(targetMock, condition, expectation: part);
targetMock.Setups.Add(setup);
targetMock.MutableSetups.Add(setup);
return setup;
});
}
Expand Down Expand Up @@ -563,7 +569,7 @@ internal static SequenceSetup SetupSequence(Mock mock, LambdaExpression expressi
return Mock.SetupRecursive(mock, expression, setupLast: (part, targetMock) =>
{
var setup = new SequenceSetup(expectation: part);
targetMock.Setups.Add(setup);
targetMock.MutableSetups.Add(setup);
return setup;
});
}
Expand All @@ -590,7 +596,7 @@ private static TSetup SetupRecursive<TSetup>(Mock mock, LambdaExpression express
else
{
Mock innerMock;
if (!(mock.Setups.GetInnerMockSetups().TryFind(part, out var setup) && setup.ReturnsInnerMock(out innerMock)))
if (!(mock.MutableSetups.GetInnerMockSetups().TryFind(part, out var setup) && setup.ReturnsInnerMock(out innerMock)))
{
var returnValue = mock.GetDefaultValue(method, out innerMock, useAlternateProvider: DefaultValueProvider.Mock);
if (innerMock == null)
Expand All @@ -602,7 +608,7 @@ private static TSetup SetupRecursive<TSetup>(Mock mock, LambdaExpression express
expr.ToStringFixed() + " in " + expression.ToStringFixed() + ":\n" + Resources.TypeNotMockable));
}
setup = new InnerMockSetup(expectation: part, returnValue);
mock.Setups.Add((Setup)setup);
mock.MutableSetups.Add((Setup)setup);
}
Debug.Assert(innerMock != null);

Expand All @@ -617,7 +623,7 @@ internal static void SetupAllProperties(Mock mock)

internal static void SetupAllProperties(Mock mock, DefaultValueProvider defaultValueProvider)
{
mock.Setups.RemoveAllPropertyAccessorSetups();
mock.MutableSetups.RemoveAllPropertyAccessorSetups();
// Removing all the previous properties setups to keep the behaviour of overriding
// existing setups in `SetupAllProperties`.

Expand Down Expand Up @@ -692,7 +698,7 @@ internal static void RaiseEvent(Mock mock, LambdaExpression expression, Stack<In
handlers.InvokePreserveStack(arguments);
}
}
else if (mock.Setups.GetInnerMockSetups().TryFind(part, out var innerMockSetup) && innerMockSetup.ReturnsInnerMock(out var innerMock))
else if (mock.MutableSetups.GetInnerMockSetups().TryFind(part, out var innerMockSetup) && innerMockSetup.ReturnsInnerMock(out var innerMock))
{
Mock.RaiseEvent(innerMock, expression, parts, arguments);
}
Expand Down Expand Up @@ -798,7 +804,7 @@ internal void AddInnerMockSetup(Invocation invocation, object returnValue)
Debug.Assert(property.CanRead(out var getter) && invocation.Method == getter);
}

this.Setups.Add(new InnerMockSetup(new InvocationShape(expression, invocation.Method, arguments, exactGenericTypeArguments: true), returnValue));
this.MutableSetups.Add(new InnerMockSetup(new InvocationShape(expression, invocation.Method, arguments, exactGenericTypeArguments: true), returnValue));
}

#endregion
Expand Down
2 changes: 1 addition & 1 deletion src/Moq/MockExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static partial class MockExtensions
public static void Reset(this Mock mock)
{
mock.ConfiguredDefaultValues.Clear();
mock.Setups.Clear();
mock.MutableSetups.Clear();
mock.EventHandlers.Clear();
mock.Invocations.Clear();
}
Expand Down
20 changes: 19 additions & 1 deletion src/Moq/Setup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@

namespace Moq
{
internal abstract class Setup
internal abstract class Setup : ISetup
{
private readonly InvocationShape expectation;
private Flags flags;

protected Setup(InvocationShape expectation)
{
Expand All @@ -22,10 +23,14 @@ protected Setup(InvocationShape expectation)

public virtual Condition Condition => null;

public bool IsConditional => this.Condition != null;

public InvocationShape Expectation => this.expectation;

public LambdaExpression Expression => this.expectation.Expression;

public bool IsOverridden => (this.flags & Flags.Overridden) != 0;

public virtual bool IsVerifiable => false;

public MethodInfo Method => this.expectation.Method;
Expand All @@ -47,6 +52,13 @@ public virtual bool TryGetReturnValue(out object returnValue)
return false;
}

public void MarkAsOverridden()
{
Debug.Assert(!this.IsOverridden);

this.flags |= Flags.Overridden;
}

public bool Matches(Invocation invocation)
{
return this.expectation.IsMatch(invocation) && (this.Condition == null || this.Condition.IsTrue);
Expand Down Expand Up @@ -174,5 +186,11 @@ protected virtual bool TryVerifySelf(out MockException error)
public virtual void Uninvoke()
{
}

[Flags]
private enum Flags : byte
{
Overridden = 1,
}
}
}
Loading

0 comments on commit b1437af

Please sign in to comment.