Skip to content
This repository was archived by the owner on Jun 30, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
973fd6d
Initial support for a legacy API shim
kzu Jul 23, 2019
b83445a
Add codegen support for new Mock<T>
kzu Jul 23, 2019
a58ad2d
Setup should happen as soon as possible
kzu Jul 23, 2019
72a1a28
Make sure we use the test calling assembly
adalon Jul 24, 2019
df46b70
Added SkipBehavior support for behavior pipeline
adalon Jul 24, 2019
5aaca90
Add new behavior for setup scope runs
kzu Jul 24, 2019
0c2ff79
Add setup overloads to avoid the mock T argument
kzu Jul 24, 2019
60237fe
Added AsMoq extension method
adalon Jul 24, 2019
eda348b
Simplify Behavior implementation by using SkipBehaviors
kzu Jul 24, 2019
edc7b68
Revert "Add setup overloads to avoid the mock T argument"
kzu Jul 24, 2019
efeefc3
Fix failing tests
kzu Jul 24, 2019
4e9d640
Move setup scope to Moq
kzu Jul 24, 2019
004e96e
Fix visiblity of various Sdk-like classes in the Moq main assembly
kzu Jul 24, 2019
91b4853
Added CallBase support
adalon Jul 24, 2019
58a67fe
Don't run FixupImports twice
kzu Jul 25, 2019
3f191e7
Ensure both analyzers and codefixers have NuGetPackageId metadata
kzu Jul 25, 2019
4c5db10
Optimize codegen performance for real world solutions
kzu Jul 25, 2019
e159b48
Do not clean unused namespaces, since it is costly for little benefit
kzu Jul 25, 2019
814afdc
Bump to latest Roslyn for VS2017 and updated supported code fix names
kzu Jul 25, 2019
708db5b
Ensure a clean restore is performed always, add CI feed
kzu Jul 25, 2019
20b9fd2
Set proper names for CallBase tests
adalon Jul 25, 2019
6143a8a
Bump TFV to the 16.0+ official one supporting NS2
kzu Jul 29, 2019
e5b1c4e
Properly generate code for generic mocks
kzu Aug 3, 2019
d9bbc2e
Add support for mocking generic types
kzu Aug 6, 2019
34a6c49
Move OverrideAllMembersCodeFix to CodeFix assembly to avoid csc error
kzu Aug 6, 2019
1e97239
Don't assume mocked types will be public
kzu Aug 6, 2019
e30d526
Cleanup and encapsulate the batch code fixer and avoid state capturing
kzu Aug 6, 2019
f4b4be4
Delete OverrideAllMembersCodeFix class that moved to CodeFix
kzu Aug 6, 2019
d5e5fd7
Re-enable end to end tests for VB since they work now
kzu Aug 6, 2019
cfddaf2
Fix roslyn internals tests from moved RoslynInternals.cs file
kzu Aug 8, 2019
2e4f6e8
Fix minor style issues flagged by codefactor.io
kzu Aug 8, 2019
462cf05
Unify naming conventions for runtime lookup
kzu Aug 8, 2019
83a22b0
Minor docs tweaks to CallBase
kzu Aug 8, 2019
db87731
Drastically simplify As<T> support by adding new Mock<T...Tn>
kzu Aug 8, 2019
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
Prev Previous commit
Next Next commit
Added CallBase support
  • Loading branch information
adalon committed Jul 24, 2019
commit 91b4853ba4de4cb8dbd96d6dac58a97e243d96f0
78 changes: 78 additions & 0 deletions src/Moq/Moq.Tests/CallBaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System;
using System.ComponentModel;
using Xunit;
using Moq.Sdk;
using static Moq.Syntax;
using Sample;

namespace Moq.Tests
{
public class CallBaseTests
{
[Fact]
public void callbase1()
{
var mock = Mock.Of<Calculator>();

mock.TurnOn();

Assert.False(mock.TurnOnCalled);
}

[Fact]
public void callbase2()
{
var mock = Mock.Of<Calculator>().CallBase();

mock.TurnOn();

Assert.True(mock.TurnOnCalled);
}

[Fact]
public void callbase3()
{
var mock = Mock.Of<Calculator>();

mock.Setup(x => x.TurnOn()).CallBase();

mock.TurnOn();

Assert.True(mock.TurnOnCalled);
}

[Fact]
public void callbase4()
{
var mock = Mock.Of<Calculator>(MockBehavior.Strict).CallBase();

Assert.Throws<StrictMockException>(() => mock.TurnOn());
}

[Fact]
public void callbase5()
{
var mock = Mock.Of<Calculator>(MockBehavior.Strict).CallBase();

mock.Setup(x => x.TurnOn());

mock.TurnOn();

Assert.True(mock.TurnOnCalled);
Assert.Throws<StrictMockException>(() => mock.Recall(""));
}

[Fact]
public void callbase6()
{
var mock = Mock.Of<Calculator>(MockBehavior.Strict);

mock.Setup(x => x.TurnOn()).CallBase();

mock.TurnOn();

Assert.True(mock.TurnOnCalled);
Assert.Throws<StrictMockException>(() => mock.Recall(""));
}
}
}
93 changes: 93 additions & 0 deletions src/Moq/Moq.Tests/Mocks/CalculatorMock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using Moq.Sdk;
using Sample;
using Stunts;

namespace Mocks
{
public partial class CalculatorMock : Calculator, IMocked, IStunt
{
BehaviorPipeline pipeline = new BehaviorPipeline();

public ObservableCollection<IStuntBehavior> Behaviors => pipeline.Behaviors;

public override event EventHandler TurnedOn
{
add => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value), (m, n) => { base.TurnedOn += value; return m.CreateValueReturn(null, value); });
remove => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value), (m, n) => { base.TurnedOn -= value; return m.CreateValueReturn(null, value); });
}

public override CalculatorMode Mode
{
get => pipeline.Execute<CalculatorMode>(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => m.CreateValueReturn(base.Mode));
set => pipeline.Invoke(new MethodInvocation(this, MethodBase.GetCurrentMethod(), value), (m, n) => { base.Mode = value; return m.CreateValueReturn(null, value); });
}

public override int? this[string name]
{
get => pipeline.Execute<int?>(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => m.CreateValueReturn(base[name]));
set => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value), (m, n) => { base[name] = value; return m.CreateValueReturn(null, value); });
}

public override bool IsOn => pipeline.Execute<bool>(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => m.CreateValueReturn(base.IsOn));

public override int Add(int x, int y) =>
pipeline.Execute<int>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y), (m, n) => m.CreateValueReturn(base.Add(x, y), x, y));

public override int Add(int x, int y, int z) =>
pipeline.Execute<int>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y, z), (m, n) => m.CreateValueReturn(base.Add(x, y, z), x, y, z));

public override bool TryAdd(ref int x, ref int y, out int z)
{
z = default(int);
var local_x = x;
var local_y = y;
var local_z = z;

var result = pipeline.Invoke(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y, z),
(m, n) => m.CreateValueReturn(base.TryAdd(ref local_x, ref local_y, out local_z), local_x, local_y, local_z), true);

x = (int)result.Outputs["x"];
y = (int)result.Outputs["y"];
z = (int)result.Outputs["z"];

return (bool)result.ReturnValue;
}

public override void TurnOn() =>
pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => { base.TurnOn(); return m.CreateValueReturn(null); });


public override void Store(string name, int value) =>
pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value), (m, n) => { base.Store(name, value); return m.CreateValueReturn(null, name, value); });

public override int? Recall(string name) =>
pipeline.Execute<int?>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name), (m, n) => m.CreateValueReturn(base.Recall(name), name));


public override void Clear(string name) =>
pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name), (m, n) => { base.Clear(name); return m.CreateValueReturn(null, name); });


public override ICalculatorMemory Memory
{
get => pipeline.Execute<ICalculatorMemory>(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => m.CreateValueReturn(base.Memory));
}

#region IMocked
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
IMock mock;

[DebuggerDisplay("Invocations = {Invocations.Count}", Name = nameof(IMocked.Mock))]
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
[CompilerGenerated]
IMock IMocked.Mock => LazyInitializer.EnsureInitialized(ref mock, () => new DefaultMock(this));
#endregion
}
}
40 changes: 40 additions & 0 deletions src/Moq/Moq/CallBaseBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using Stunts;
using Moq.Sdk;
using System.Linq;
using System.ComponentModel;
using System.Diagnostics;

namespace Moq
{
/// <summary>
/// A custom behavior to enable calls to the base member virtual implementation
/// <see cref="CallBaseExtension"/> method calls.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public class CallBaseBehavior : IStuntBehavior
{
/// <inheritdoc />
public bool AppliesTo(IMethodInvocation invocation) => true;

/// <inheritdoc />
public IMethodReturn Execute(IMethodInvocation invocation, GetNextBehavior next)
{
// Check if CallBase is configured at the Mock or Invocation level
var shouldCallBase = invocation.Target.AsMoq().CallBase || invocation.Context.ContainsKey(nameof(IMoq.CallBase));

if (shouldCallBase)
{
// Skip the default value to force the base target member is executed
invocation.SkipBehaviors.Add(typeof(DefaultValueBehavior));

// If there is a matching setup for the current invocation, skip the strict
// behavior because CallBase should be called instead
if (invocation.Target.AsMock().Behaviors.OfType<IMockBehaviorPipeline>().Any(x => x.AppliesTo(invocation)))
invocation.SkipBehaviors.Add(typeof(StrictMockBehavior));
}

return next().Invoke(invocation, next);
}
}
}
40 changes: 40 additions & 0 deletions src/Moq/Moq/CallBaseExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Moq.Sdk;
using System.ComponentModel;
using System;

namespace Moq
{
/// <summary>
/// Extensions for calling base member virtual implementation
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class CallBaseExtension
{
/// <summary>
/// Specifies to call the base member virtual implementation
/// </summary>
public static T CallBase<T>(this T target)
{
if (target is IMocked mocked && mocked != null)
{
// Configure CallBase at the Mock level
mocked.AsMoq().CallBase = true;
}
else if (MockContext.CurrentInvocation != null)
{
// Configure CallBase at the invocation level
MockContext.CurrentInvocation.Target.AsMock().GetPipeline(MockContext.CurrentSetup).Behaviors.Add(new DelegateMockBehavior(
(m, i, next) =>
{
// set CallBase
i.Context[nameof(IMoq.CallBase)] = true;

return next().Invoke(i.Target.AsMock(), i, next);

}, nameof(IMoq.CallBase)));
}

return target;
}
}
}
5 changes: 5 additions & 0 deletions src/Moq/Moq/ConfigurePipelineBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public IMethodReturn Execute(IMethodInvocation invocation, GetNextBehavior next)
invocation.SkipBehaviors.Add(typeof(StrictMockBehavior));
}

// Ensure the Mock pipeline is always created for the matching setup
// We need this to skip the StrictBehavior in the CallBaseBehavior
if (SetupScope.IsActive)
invocation.Target.AsMock().GetPipeline(MockContext.CurrentSetup);

return next().Invoke(invocation, next);
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/Moq/Moq/IMoq.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ public interface IMoq : IMock
/// Only available for <see cref="MockBehavior.Loose"/> mocks.
/// </summary>
DefaultValueProvider DefaultValue { get; set; }

/// <summary>
/// Whether the base member virtual implementation will be called for mocked classes if no setup is matched.
/// Defaults to <see langword="false"/>.
/// </summary>
bool CallBase { get; set; }
}
}
}
8 changes: 8 additions & 0 deletions src/Moq/Moq/Legacy/Mock`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,14 @@ public Mock(MockBehavior behavior, params object[] args) : this(Assembly.GetCall
[EditorBrowsable(EditorBrowsableState.Never)]
public T Object => target;

/// <summary>Supports the legacy API.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool CallBase
{
get => mock.AsMoq().CallBase;
set => mock.AsMoq().CallBase = value;
}

/// <summary>Supports the legacy API.</summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public Mock<TInterface> As<TInterface>()
Expand Down
3 changes: 2 additions & 1 deletion src/Moq/Moq/MockInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,14 @@ public static void Initialize(this IMocked mocked, MockBehavior behavior)
mocked.AsMoq().Behavior = behavior;

mocked.Mock.Behaviors.Add(new SetupScopeBehavior());
mocked.Mock.Behaviors.Add(new ConfigurePipelineBehavior());
mocked.Mock.Behaviors.Add(new MockContextBehavior());
mocked.Mock.Behaviors.Add(new ConfigurePipelineBehavior());
mocked.Mock.Behaviors.Add(new MockRecordingBehavior());
mocked.Mock.Behaviors.Add(new EventBehavior());
mocked.Mock.Behaviors.Add(new PropertyBehavior { SetterRequiresSetup = behavior == MockBehavior.Strict });
mocked.Mock.Behaviors.Add(new DefaultEqualityBehavior());
mocked.Mock.Behaviors.Add(new RecursiveMockBehavior());
mocked.Mock.Behaviors.Add(new CallBaseBehavior());

// Dynamically configured by the ConfigurePipelineBehavior.
mocked.Mock.Behaviors.Add(new StrictMockBehavior());
Expand Down
11 changes: 10 additions & 1 deletion src/Moq/Moq/Moq`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

namespace Moq
{


/// <summary>
/// Provides the Moq specific configurations on top of an <see cref="Sdk.IMock{T}"/> that
/// the Moq API provides beyond the SDK.
Expand Down Expand Up @@ -51,5 +53,12 @@ public DefaultValueProvider DefaultValue
State.Set(value);
}
}

/// <inheritdoc />
public bool CallBase
{
get => State.GetOrAdd(nameof(IMoq) + "." + nameof(CallBase), () => false);
set => State.Set(nameof(IMoq) + "." + nameof(CallBase), value);
}
}
}
}
1 change: 1 addition & 0 deletions src/Moq/Moq/SetupScopeBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal class SetupScopeBehavior : IStuntBehavior
typeof(DefaultValueBehavior),
typeof(MockContextBehavior),
typeof(RecursiveMockBehavior),
typeof(ConfigurePipelineBehavior)
};

/// <summary>
Expand Down
3 changes: 3 additions & 0 deletions src/Samples/Sample/Calculator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class Calculator : ICalculator, IDisposable

public virtual event EventHandler TurnedOn;

public bool TurnOnCalled { get; set; }

public virtual bool IsOn { get; private set; }

public virtual CalculatorMode Mode { get; set; }
Expand All @@ -22,6 +24,7 @@ public virtual void TurnOn()
{
TurnedOn?.Invoke(this, EventArgs.Empty);
IsOn = true;
TurnOnCalled = true;
}

public virtual int? this[string name]
Expand Down
3 changes: 3 additions & 0 deletions src/Samples/Sample/CalculatorClassStunt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ public override int? this[string name]
set => pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value), (m, n) => { base[name] = value; return m.CreateValueReturn(null, value); });
}

public override bool IsOn => pipeline.Execute<bool>(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => m.CreateValueReturn(base.IsOn));

public override int Add(int x, int y) =>
pipeline.Execute<int>(new MethodInvocation(this, MethodBase.GetCurrentMethod(), x, y), (m, n) => m.CreateValueReturn(base.Add(x, y), x, y));

Expand All @@ -55,6 +57,7 @@ public override bool TryAdd(ref int x, ref int y, out int z)
public override void TurnOn() =>
pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod()), (m, n) => { base.TurnOn(); return m.CreateValueReturn(null); });


public override void Store(string name, int value) =>
pipeline.Execute(new MethodInvocation(this, MethodBase.GetCurrentMethod(), name, value), (m, n) => { base.Store(name, value); return m.CreateValueReturn(null, name, value); });

Expand Down