Skip to content

Commit

Permalink
Merge pull request #76 from pimterry/namedMocks
Browse files Browse the repository at this point in the history
Give mocks names
  • Loading branch information
kzu committed Jan 8, 2014
2 parents 5a9d9b4 + 9e4e589 commit 85a12ff
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 28 deletions.
3 changes: 2 additions & 1 deletion Source/Interceptor.cs
Expand Up @@ -117,7 +117,8 @@ private IEnumerable<IInterceptStrategy> InterceptionStrategies()
{
yield return new HandleDestructor();
yield return new HandleTracking();
yield return new CheckMockMixing();
yield return new InterceptMockPropertyMixin();
yield return new InterceptToStringMixin();
yield return new AddActualInvocation();
yield return new ExtractProxyCall();
yield return new ExecuteCall();
Expand Down
38 changes: 18 additions & 20 deletions Source/InterceptorStrategies.cs
Expand Up @@ -111,39 +111,41 @@ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorCo

}

internal class CheckMockMixing : IInterceptStrategy
internal class InterceptMockPropertyMixin : IInterceptStrategy
{

public CheckMockMixing()
{

}
public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
{
if (invocation.Method.DeclaringType.IsGenericType &&
invocation.Method.DeclaringType.GetGenericTypeDefinition() == typeof(IMocked<>))
var method = invocation.Method;

if (typeof(IMocked).IsAssignableFrom(method.DeclaringType) && method.Name == "get_Mock")
{
// "Mixin" of IMocked<T>.Mock
invocation.ReturnValue = ctx.Mock;
return InterceptionAction.Stop;
}
else if (invocation.Method.DeclaringType == typeof(IMocked))

return InterceptionAction.Continue;
}
}

internal class InterceptToStringMixin : IInterceptStrategy
{
public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
{
var method = invocation.Method;

if (method.DeclaringType == typeof(Object) && method.Name == "ToString")
{
// "Mixin" of IMocked.Mock
invocation.ReturnValue = ctx.Mock;
invocation.ReturnValue = ctx.Mock.ToString() + ".Object";
return InterceptionAction.Stop;
}

return InterceptionAction.Continue;
}
}

internal class HandleTracking : IInterceptStrategy
{

public HandleTracking()
{

}
public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorContext ctx, CurrentInterceptContext localctx)
{
// Track current invocation if we're in "record" mode in a fluent invocation context.
Expand All @@ -166,10 +168,6 @@ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorCo
internal class AddActualInvocation : IInterceptStrategy
{

public AddActualInvocation()
{

}
/// <summary>
/// Get an eventInfo for a given event name. Search type ancestors depth first if necessary.
/// </summary>
Expand Down
33 changes: 32 additions & 1 deletion Source/Mock.Generic.cs
Expand Up @@ -39,8 +39,10 @@
// http://www.opensource.org/licenses/bsd-license.php]

using System;
using System.CodeDom;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using Microsoft.CSharp;
using Moq.Language.Flow;
using Moq.Proxy;
using Moq.Language;
Expand Down Expand Up @@ -102,6 +104,8 @@ public Mock(MockBehavior behavior, params object[] args)
args = new object[] { null };
}

this.Name = GenerateMockName();

this.Behavior = behavior;
this.Interceptor = new Interceptor(behavior, typeof(T), this);
this.constructorArguments = args;
Expand All @@ -110,6 +114,24 @@ public Mock(MockBehavior behavior, params object[] args)
this.CheckParameters();
}

private string GenerateMockName()
{
var randomId = Guid.NewGuid().ToString("N").Substring(0, 4);

var typeName = typeof (T).FullName;

if (typeof (T).IsGenericType)
{
using (var provider = new CSharpCodeProvider())
{
var typeRef = new CodeTypeReference(typeof(T));
typeName = provider.GetTypeOutput(typeRef);
}
}

return "Mock<" + typeName + ":" + randomId + ">";
}

private void CheckParameters()
{
typeof(T).ThrowIfNotMockeable();
Expand Down Expand Up @@ -139,7 +161,16 @@ public virtual new T Object
get { return (T)base.Object; }
}

internal override bool IsDelegateMock
/// <include file='Mock.Generic.xdoc' path='docs/doc[@for="Mock{T}.Name"]/*'/>
public string Name { get; set; }

/// <include file='Mock.Generic.xdoc' path='docs/doc[@for="Mock{T}.ToString"]/*'/>
public override string ToString()
{
return this.Name;
}

internal override bool IsDelegateMock
{
get { return typeof(T).IsDelegate(); }
}
Expand Down
10 changes: 10 additions & 0 deletions Source/Mock.Generic.xdoc
Expand Up @@ -107,6 +107,16 @@
Exposes the mocked object instance.
</summary>
</doc>
<doc for="Mock{T}.Name">
<summary>
Allows naming of your mocks, so they can be easily identified in error messages (e.g. from failed assertions).
</summary>
</doc>
<doc for="Mock{T}.ToString">
<summary>
Returns the name of the mock
</summary>
</doc>
<doc for="Mock{T}.Setup">
<summary>
Specifies a setup on the mocked type for a call to
Expand Down
1 change: 1 addition & 0 deletions Source/Moq.csproj
Expand Up @@ -86,6 +86,7 @@
<DesignTime>True</DesignTime>
<DependentUpon>IReturns.tt</DependentUpon>
</Compile>
<Compile Include="Proxy\ProxyGenerationHelpers.cs" />
<Compile Include="ReturnsExtensions.cs" />
<Compile Include="Language\ISetupSequentialResult.cs" />
<Compile Include="Language\ISetupConditionResult.cs" />
Expand Down
11 changes: 6 additions & 5 deletions Source/Proxy/CastleProxyFactory.cs
Expand Up @@ -66,24 +66,24 @@ static CastleProxyFactory()
AttributesToAvoidReplicating.Add<ReflectionPermissionAttribute>();
AttributesToAvoidReplicating.Add<PermissionSetAttribute>();
AttributesToAvoidReplicating.Add<System.Runtime.InteropServices.MarshalAsAttribute>();
AttributesToAvoidReplicating.Add<UIPermissionAttribute>();
AttributesToAvoidReplicating.Add<UIPermissionAttribute>();
#if !NET3x
AttributesToAvoidReplicating.Add<System.Runtime.InteropServices.TypeIdentifierAttribute>();
#endif
#endif
proxyOptions = new ProxyGenerationOptions { Hook = new ProxyMethodHook(), BaseTypeForInterfaceProxy = typeof(InterfaceProxy) };
}

/// <inheritdoc />
public object CreateProxy(Type mockType, ICallInterceptor interceptor, Type[] interfaces, object[] arguments)
{
if (mockType.IsInterface)
{
return generator.CreateInterfaceProxyWithoutTarget(mockType, interfaces, new Interceptor(interceptor));
if (mockType.IsInterface) {
return generator.CreateInterfaceProxyWithoutTarget(mockType, interfaces, proxyOptions, new Interceptor(interceptor));
}

try
{
return generator.CreateClassProxy(mockType, interfaces, ProxyGenerationOptions.Default, arguments, new Interceptor(interceptor));
return generator.CreateClassProxy(mockType, interfaces, proxyOptions, arguments, new Interceptor(interceptor));
}
catch (TypeLoadException e)
{
Expand All @@ -96,6 +96,7 @@ public object CreateProxy(Type mockType, ICallInterceptor interceptor, Type[] in
}

private static readonly Dictionary<Type, Type> delegateInterfaceCache = new Dictionary<Type, Type>();
private static readonly ProxyGenerationOptions proxyOptions;
private static int delegateInterfaceSuffix;

/// <inheritdoc />
Expand Down
44 changes: 44 additions & 0 deletions Source/Proxy/ProxyGenerationHelpers.cs
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using Castle.DynamicProxy;

namespace Moq.Proxy
{
/// <summary>
/// Hook used to tells Castle which methods to proxy in mocked classes.
///
/// Here we proxy the default methods Castle suggests (everything Object's methods)
/// plus Object.ToString(), so we can give mocks useful default names.
///
/// This is required to allow Moq to mock ToString on proxy *class* implementations.
/// </summary>
internal class ProxyMethodHook : AllMethodsHook
{
/// <summary>
/// Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString().
/// </summary>
public override bool ShouldInterceptMethod(Type type, MethodInfo methodInfo)
{
var isObjectToString = methodInfo.DeclaringType == typeof(Object) && methodInfo.Name == "ToString";
return base.ShouldInterceptMethod(type, methodInfo) || isObjectToString;
}
}

/// <summary>
/// The base class used for all our interface-inheriting proxies, which overrides the default
/// Object.ToString() behavior, to route it via the mock by default, unless overriden by a
/// real implementation.
///
/// This is required to allow Moq to mock ToString on proxy *interface* implementations.
/// </summary>
internal abstract class InterfaceProxy
{
public override string ToString()
{
return ((IMocked)this).Mock.ToString() + ".Object";
}
}
}
78 changes: 77 additions & 1 deletion UnitTests/MockFixture.cs
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using Xunit;

Expand All @@ -16,6 +18,80 @@ public void CreatesMockAndExposesInterface()
Assert.NotNull(comparable);
}

[Fact]
public void CanBeNamedForEasierDebugging()
{
var mock = new Mock<IComparable>();
mock.Name = "my mock";

Assert.Equal("my mock", mock.ToString());
}

[Fact]
public void HasADefaultNameThatIsUnique()
{
var mock = new Mock<IComparable>();
var mock2 = new Mock<IComparable>();

Assert.NotEqual(mock.ToString(), mock2.ToString());
}

[Fact]
public void HasADefaultNameThatIncludesItsTypeAndThatItsAMock()
{
var mock = new Mock<IComparable>();

Assert.Contains("System.IComparable", mock.ToString());
Assert.Contains("mock", mock.ToString().ToLower());
}

[Fact]
public void HasADefaultNameThatIncludesItsGenericParameters()
{
var mock = new Mock<Dictionary<int, string>>();

Assert.Contains("System.Collections.Generic.Dictionary<int, string>", mock.ToString());
}

[Fact]
public void PassesItsNameOnToTheResultingMockObjectWhenMockingInterfaces()
{
var mock = new Mock<IComparable>();

Assert.Equal(mock.ToString() + ".Object", mock.Object.ToString());
}

[Fact]
public void PassesItsNameOnToTheResultingMockObjectWhenMockingClasses()
{
var mock = new Mock<ArrayList>();

Assert.Equal(mock.ToString() + ".Object", mock.Object.ToString());
}

public class ToStringOverrider
{
public override string ToString() {
return "real value";
}
}

[Fact]
public void OverriddenToStringMethodsCallUnderlyingImplementationInPartialMocks()
{
var partialMock = new Mock<ToStringOverrider>() { CallBase = true };

Assert.Equal("real value", partialMock.Object.ToString());
}

[Fact]
public void OverriddenToStringMethodsAreStubbedWithDefaultValuesInFullMocks()
{
var fullMock = new Mock<ToStringOverrider>();

Assert.Null(fullMock.Object.ToString());
}

[Fact]
public void ThrowsIfNullExpectAction()
{
Expand Down Expand Up @@ -148,7 +224,7 @@ public void HashCodeIsDifferentForEachMock()
}

[Fact]
public void ToStringIsNullOrEmpty()
public void ToStringIsNotNullOrEmpty()
{
var mock = new Mock<IFoo>();
Assert.False(String.IsNullOrEmpty(mock.Object.ToString()));
Expand Down

0 comments on commit 85a12ff

Please sign in to comment.