Skip to content

Commit

Permalink
Feature/113 factory type creator (#124)
Browse files Browse the repository at this point in the history
* Added FactoryTypeCreator
* Updated DefaultConstructorResolver to return null when no constructor found
* Updated DefaultTypeCreator to not create types without a public constructors
  • Loading branch information
roryprimrose committed Jun 13, 2020
1 parent 0c03975 commit a853e95
Show file tree
Hide file tree
Showing 16 changed files with 735 additions and 133 deletions.
64 changes: 11 additions & 53 deletions ModelBuilder.UnitTests/DefaultConstructorResolverTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,20 @@ public void ResolveReturnsDefaultConstructorWhenManyConstructorsAvailable()
constructor.GetParameters().Should().BeEmpty();
}

[Fact]
public void ResolveReturnsNullForStructThatHasNoConstructors()
[Theory]
[InlineData(typeof(StructModel))]
[InlineData(typeof(FactoryItem))]
[InlineData(typeof(FactoryWithValue))]
[InlineData(typeof(NotFactoryItem))]
[InlineData(typeof(Singleton))]
[InlineData(typeof(Gender))]
[InlineData(typeof(Copy))]
[InlineData(typeof(Clone))]
public void ResolveReturnsNullForTypesWithoutPublicConstructors(Type targetType)
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

var constructor = sut.Resolve(typeof(StructModel));
var constructor = sut.Resolve(targetType);

constructor.Should().BeNull();
}
Expand All @@ -227,16 +235,6 @@ public void ResolveReturnsParameterConstructor()
constructor.GetParameters().Should().NotBeEmpty();
}

[Fact]
public void ResolveThrowsExceptionForEnum()
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

Action action = () => sut.Resolve(typeof(Gender));

_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenArgsContainsNullAndArgCountLessThanOptionalParam()
{
Expand Down Expand Up @@ -315,26 +313,6 @@ public void ResolveThrowsExceptionWhenArgsContainsNullNoOptionalParamsAndArgumen
_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenClassOnlyContainsConstructorsThatReferenceTheSameType()
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

Action action = () => sut.Resolve(typeof(Clone));

_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenClassOnlyContainsCopyConstructor()
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

Action action = () => sut.Resolve(typeof(Copy));

_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenNoConstructorMatchingSpecifiedParameters()
{
Expand All @@ -345,16 +323,6 @@ public void ResolveThrowsExceptionWhenNoConstructorMatchingSpecifiedParameters()
_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenNoPublicConstructorFound()
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

Action action = () => sut.Resolve(typeof(Singleton));

_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenParameterValuesDoNotMatchParameterTypes()
{
Expand All @@ -374,16 +342,6 @@ public void ResolveThrowsExceptionWhenParameterValuesDoNotMatchParameterTypes()
_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWhenWhenOnlyPrivateConstructorAvailable()
{
var sut = new DefaultConstructorResolver(CacheLevel.PerInstance);

Action action = () => sut.Resolve(typeof(Singleton));

_output.WriteLine(action.Should().Throw<MissingMemberException>().And.Message);
}

[Fact]
public void ResolveThrowsExceptionWithNullType()
{
Expand Down
76 changes: 76 additions & 0 deletions ModelBuilder.UnitTests/DefaultExecuteStrategyTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,49 @@ public void ConfigurationThrowsExceptionWhenNotInitialized()
action.Should().Throw<InvalidOperationException>();
}

[Fact]
public void CreateCanAssignNullProperty()
{
var buildHistory = new BuildHistory();
var expected = new NullablePropertyModel<Person>();

var processor = Substitute.For<IBuildProcessor>();
var buildConfiguration = Substitute.For<IBuildConfiguration>();
var propertyResolver = Substitute.For<IPropertyResolver>();
var typeCapability = Substitute.For<IBuildCapability>();
var valueCapability = Substitute.For<IBuildCapability>();

typeCapability.AutoPopulate.Returns(true);
typeCapability.SupportsCreate.Returns(true);
typeCapability.SupportsPopulate.Returns(true);
typeCapability.ImplementedByType.Returns(typeof(DummyTypeCreator));
valueCapability.SupportsCreate.Returns(true);
valueCapability.ImplementedByType.Returns(typeof(DummyTypeCreator));

var sut = new DefaultExecuteStrategy(buildHistory, _buildLog, processor);

processor.GetBuildCapability(sut, Arg.Any<BuildRequirement>(),
typeof(NullablePropertyModel<Person>))
.Returns(typeCapability);
processor.GetBuildCapability(sut, Arg.Any<BuildRequirement>(),
Arg.Any<PropertyInfo>())
.Returns(valueCapability);
typeCapability.CreateType(sut, typeof(NullablePropertyModel<Person>), Arg.Any<object[]>())
.Returns(expected);
valueCapability.CreateProperty(sut, Arg.Any<PropertyInfo>(), Arg.Any<object[]>()).Returns(null);
typeCapability.Populate(sut, expected).Returns(expected);
buildConfiguration.PropertyResolver.Returns(propertyResolver);
propertyResolver.GetOrderedProperties(buildConfiguration, typeof(NullablePropertyModel<Person>))
.Returns(typeof(NullablePropertyModel<Person>).GetProperties());

sut.Initialize(buildConfiguration);

var actual = (NullablePropertyModel<Person>) sut.Create(typeof(NullablePropertyModel<Person>))!;

actual.Should().Be(expected);
actual.Value.Should().BeNull();
}

[Fact]
public void CreateDeterminesPropertiesToCreateByProvidingConstructorArgsForNestedType()
{
Expand Down Expand Up @@ -374,6 +417,39 @@ public void CreateEvaluatesPostBuildActionsWhenCapabilityDoesNotSupportPopulatio
action.Received().Execute(Arg.Any<IBuildChain>(), Arg.Any<SlimModel>(), typeof(SlimModel));
}

[Fact]
public void CreateParametersCanReturnNullParameter()
{
var buildHistory = new BuildHistory();
var method = typeof(SimpleConstructor).GetConstructors().First();
var parameters = method.GetParameters().OrderBy(x => x.Name);

var parameterCapability = Substitute.For<IBuildCapability>();
var processor = Substitute.For<IBuildProcessor>();
var parameterResolver = Substitute.For<IParameterResolver>();
var buildConfiguration = Substitute.For<IBuildConfiguration>();

var sut = new DefaultExecuteStrategy(buildHistory, _buildLog, processor);

parameterCapability.AutoPopulate.Returns(false);
parameterCapability.SupportsCreate.Returns(true);
parameterCapability.SupportsPopulate.Returns(false);
parameterCapability.AutoDetectConstructor.Returns(false);
parameterCapability.ImplementedByType.Returns(GetType());
parameterCapability.CreateParameter(sut, Arg.Any<ParameterInfo>(), null).Returns(null);
processor.GetBuildCapability(sut, BuildRequirement.Create,
Arg.Any<ParameterInfo>()).Returns(parameterCapability);
parameterResolver.GetOrderedParameters(buildConfiguration, method).Returns(parameters);
buildConfiguration.ParameterResolver.Returns(parameterResolver);

sut.Initialize(buildConfiguration);

var actual = sut.CreateParameters(method)!;

actual.Should().HaveCount(1);
actual[0].Should().BeNull();
}

[Fact]
public void CreateParametersReturnsNullWhenNoOrderedParametersReturned()
{
Expand Down
19 changes: 19 additions & 0 deletions ModelBuilder.UnitTests/Models/FactoryItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ModelBuilder.UnitTests.Models
{
using System;

public class FactoryItem
{
private FactoryItem()
{
Value = Guid.NewGuid();
}

public static FactoryItem Create()
{
return new FactoryItem();
}

public Guid Value { get; }
}
}
19 changes: 19 additions & 0 deletions ModelBuilder.UnitTests/Models/FactoryWithValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ModelBuilder.UnitTests.Models
{
using System;

public class FactoryWithValue
{
private FactoryWithValue(Guid value)
{
Value = value;
}

public static FactoryWithValue Create(Guid value)
{
return new FactoryWithValue(value);
}

public Guid Value { get; }
}
}
19 changes: 19 additions & 0 deletions ModelBuilder.UnitTests/Models/NotFactoryItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace ModelBuilder.UnitTests.Models
{
using System;

public class NotFactoryItem
{
private NotFactoryItem()
{
Value = Guid.NewGuid();
}

public static NotFactoryItem Create(NotFactoryItem item)
{
return item;
}

public Guid Value { get; }
}
}
7 changes: 7 additions & 0 deletions ModelBuilder.UnitTests/Models/NullablePropertyModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace ModelBuilder.UnitTests.Models
{
public class NullablePropertyModel<T> where T : class
{
public T Value { get; set; } = default!;
}
}
21 changes: 3 additions & 18 deletions ModelBuilder.UnitTests/Scenarios/ConstructorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ public ConstructorTests(ITestOutputHelper output)
}

[Fact]
public void CreateThrowsExceptionWhenNoPublicConstructorFound()
public void CreateReturnsTypeThatDefinesCopyConstructor()
{
Action action = () => Model.Create<FactoryClass>();
var actual = Model.WriteLog<Other>(_output.WriteLine).Create();

action.Should().Throw<BuildException>();
actual.Should().NotBeNull();
}

[Fact]
Expand Down Expand Up @@ -82,20 +82,5 @@ public void PopulatesValueTypePropertyWhenConstructorParameterMatchesDefaultType

model.Id.Should().NotBeEmpty();
}

private class FactoryClass
{
private FactoryClass(Guid value)
{
Value = value;
}

public static FactoryClass Create(Guid value)
{
return new FactoryClass(value);
}

public Guid Value { get; }
}
}
}
36 changes: 36 additions & 0 deletions ModelBuilder.UnitTests/Scenarios/NonConstructorCreationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
namespace ModelBuilder.UnitTests.Scenarios
{
using System;
using FluentAssertions;
using ModelBuilder.UnitTests.Models;
using Xunit;

public class NonConstructorCreationTests
{
[Fact]
public void CanCreateViaStaticFactoryMethodWithCreatedParameters()
{
var actual = Model.Create<FactoryWithValue>();

actual.Value.Should().NotBeEmpty();
}

[Fact]
public void CanCreateViaStaticFactoryMethodWithoutParameters()
{
var actual = Model.Create<FactoryItem>();

actual.Value.Should().NotBeEmpty();
}

[Fact]
public void CanCreateViaStaticFactoryMethodWithProvidedParameters()
{
var value = Guid.NewGuid();

var actual = Model.Create<FactoryWithValue>(value);

actual.Value.Should().Be(value);
}
}
}
Loading

0 comments on commit a853e95

Please sign in to comment.