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

Add support for abstract classes without parameterless constructor #6

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions src/UnityAutoMoq.Tests/AbstractService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace UnityAutoMoq.Tests
{
public abstract class AbstractService
{
public AbstractService(IService service) { }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace UnityAutoMoq.Tests
{
public abstract class AbstractServiceWithAmbiguousConstructor
{
public AbstractServiceWithAmbiguousConstructor(IService service) { }
public AbstractServiceWithAmbiguousConstructor(IAnotherService service) { }
}
}
7 changes: 7 additions & 0 deletions src/UnityAutoMoq.Tests/UnityAutoMoq.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,15 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.5.10.11092\lib\pnunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web" />
</ItemGroup>
<ItemGroup>
<Compile Include="AbstractService.cs" />
<Compile Include="AbstractServiceWithAmbiguousConstructor.cs" />
<Compile Include="AnotherService.cs" />
<Compile Include="CoreTdd.cs" />
<Compile Include="IAnotherService.cs" />
Expand All @@ -101,6 +104,7 @@
<Compile Include="Service.cs" />
<Compile Include="ServiceWithAbstractDependency.cs" />
<Compile Include="UnityAutoMoqContainerFixture.cs" />
<Compile Include="YetAnotherService.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UnityAutoMoq\UnityAutoMoq.csproj">
Expand Down Expand Up @@ -133,6 +137,9 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
27 changes: 26 additions & 1 deletion src/UnityAutoMoq.Tests/UnityAutoMoqContainerFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void Should_apply_specified_default_value_when_specified()
[Test]
public void Should_apply_specified_default_value_when_specified_2()
{
container = new UnityAutoMoqContainer{DefaultValue = DefaultValue.Empty};
container = new UnityAutoMoqContainer { DefaultValue = DefaultValue.Empty };
var mocked = container.GetMock<IService>();

mocked.DefaultValue.ShouldEqual(DefaultValue.Empty);
Expand Down Expand Up @@ -146,5 +146,30 @@ public void Can_get_registered_implementation()

real.ShouldBeOfType<AnotherService>();
}

[Test]
public void Can_get_abstract_class_without_parameterless_constructor_without_registering_it_first()
{
var mocked = container.Resolve<AbstractService>();

mocked.ShouldNotBeNull();
}

[Test]
public void Can_resolve_concrete_type_with_abstract_dependency_that_does_not_have_a_parameterless_constructor()
{
var mocked = container.Resolve<YetAnotherService>();

mocked.ShouldNotBeNull();
mocked.AbstractService.ShouldNotBeNull();
}

[Test]
public void Abstract_class_with_ambiguous_constructor_throws_exception()
{
typeof(ResolutionFailedException).ShouldBeThrownBy(
() => container.Resolve<AbstractServiceWithAmbiguousConstructor>(),
e => e.InnerException is InvalidOperationException);
}
}
}
12 changes: 12 additions & 0 deletions src/UnityAutoMoq.Tests/YetAnotherService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace UnityAutoMoq.Tests
{
class YetAnotherService : IAnotherService
{
public AbstractService AbstractService { get; private set; }

public YetAnotherService(AbstractService service)
{
AbstractService = service;
}
}
}
29 changes: 26 additions & 3 deletions src/UnityAutoMoq/UnityAutoMoqBuilderStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public override void PreBuildUp(IBuilderContext context)

if (autoMoqContainer.Registrations.Any(r => r.RegisteredType == type))
return;

if (type.IsInterface || type.IsAbstract)
{
context.Existing = GetOrCreateMock(type);
Expand All @@ -39,10 +39,10 @@ private object GetOrCreateMock(Type t)

Type genericType = typeof(Mock<>).MakeGenericType(new[] { t });

object mock = Activator.CreateInstance(genericType);
object mock = Activator.CreateInstance(genericType, GetConstructorArguments(t));

AsExpression interfaceImplementations = autoMoqContainer.GetInterfaceImplementations(t);
if(interfaceImplementations != null)
if (interfaceImplementations != null)
interfaceImplementations.GetImplementations().Each(type => genericType.GetMethod("As").MakeGenericMethod(type).Invoke(mock, null));

genericType.InvokeMember("DefaultValue", BindingFlags.SetProperty, null, mock, new object[] { autoMoqContainer.DefaultValue });
Expand All @@ -52,5 +52,28 @@ private object GetOrCreateMock(Type t)

return mockedInstance;
}

/// <summary>
/// Get the arguments for the constructor with the most parameters following Unity convention.
/// </summary>
/// <exception cref="System.InvalidOperationException">
/// There are more than one constructors with the same (max) amount of parameters. Unity throws the same exception where resolving a concrete type.
/// </exception>
private object[] GetConstructorArguments(Type t)
{
var constructors = (from c in t.GetConstructors()
let numberOfParameters = c.GetParameters().Length
group c by numberOfParameters into x
orderby x.Key descending
select x).FirstOrDefault();

if (constructors == null)
return new object[0];
if (constructors.Count() > 1)
throw new InvalidOperationException(string.Format("The type {0} has multiple constructor of length {1}. Unable to disambiguate.", t.Name, constructors.Key));

// Constructor with most arguments to follow Unity convention
return constructors.First().GetParameters().Select(x => autoMoqContainer.Resolve(x.ParameterType, null)).ToArray();
}
}
}