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 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Source/Interceptor.cs
Expand Up @@ -117,7 +117,7 @@ private IEnumerable<IInterceptStrategy> InterceptionStrategies()
{
yield return new HandleDestructor();
yield return new HandleTracking();
yield return new CheckMockMixing();
yield return new InterceptIMockedMixinMethods();
yield return new AddActualInvocation();
yield return new ExtractProxyCall();
yield return new ExecuteCall();
Expand Down
28 changes: 8 additions & 20 deletions Source/InterceptorStrategies.cs
Expand Up @@ -111,39 +111,31 @@ public InterceptionAction HandleIntercept(ICallContext invocation, InterceptorCo

}

internal class CheckMockMixing : IInterceptStrategy
internal class InterceptIMockedMixinMethods : 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 (method.DeclaringType == typeof (Object) && method.Name == "ToString")
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not look like an IMocked-related behavior. Should we create a separate HandleToString() strategy and revert the changes to the mixing one?

{
// "Mixin" of IMocked<T>.Mock
invocation.ReturnValue = ctx.Mock;
invocation.ReturnValue = ctx.Mock.ToString() + ".Object";
return InterceptionAction.Stop;
}
else if (invocation.Method.DeclaringType == typeof(IMocked))

if (typeof(IMocked).IsAssignableFrom(method.DeclaringType) && method.Name == "get_Mock")
{
// "Mixin" of IMocked.Mock
invocation.ReturnValue = ctx.Mock;
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 +158,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);
Copy link
Contributor

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 @@ 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(ProxyBase) };
}

/// <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 ProxyBase
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should call this InterfaceProxy instead? It's a bit more explicit. Not a fan of the "Base" suffix ;)

{
public override string ToString()
{
return ((IMocked)this).Mock.ToString() + ".Object";
}
}
}
71 changes: 70 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,73 @@ 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 AllowsMockingAsNormalIfImplementationsHaveOverriddenToString() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks like two tests mixed in one?

var partialMock = new Mock<ToStringOverrider>() { CallBase = true };
var fullMock = new Mock<ToStringOverrider>();

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

[Fact]
public void ThrowsIfNullExpectAction()
{
Expand Down Expand Up @@ -148,7 +217,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