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

Give mocks names #76

Merged
merged 6 commits into from
Jan 8, 2014
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
3 changes: 2 additions & 1 deletion Source/Interceptor.cs
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice trick here! :)

}
}

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

private void CheckParameters()
{
typeof(T).ThrowIfNotMockeable();
Expand Down Expand Up @@ -139,7 +161,16 @@ private void CheckParameters()
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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