diff --git a/README.md b/README.md index 4851392..f0b394d 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ Prior to v2.0 this library was known as NTestDataBuilder. } } +Note that overriding the BuildObject is optional. It can be useful to do this if you want full control over how your object is constructed, for example if you want to use a particular constructor. If you don't choose to override this method then Dossier will create the object for you. 3. Use the builder in a test, e.g. diff --git a/TestStack.Dossier.Tests/BuildListTests.cs b/TestStack.Dossier.Tests/BuildListTests.cs index 2b498db..e2651b2 100644 --- a/TestStack.Dossier.Tests/BuildListTests.cs +++ b/TestStack.Dossier.Tests/BuildListTests.cs @@ -4,7 +4,7 @@ using TestStack.Dossier.DataSources.Generators; using TestStack.Dossier.Lists; using TestStack.Dossier.Tests.Builders; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; using Xunit; namespace TestStack.Dossier.Tests @@ -70,16 +70,7 @@ public void GivenListOfBuilders_WhenCallingBuildListExplicitly_ThenAListOfUnique var entities = builders.BuildList(); - entities[0].ShouldNotBe(entities[1]); - entities[0].ShouldNotBe(entities[2]); - entities[0].ShouldNotBe(entities[3]); - entities[0].ShouldNotBe(entities[4]); - entities[1].ShouldNotBe(entities[2]); - entities[1].ShouldNotBe(entities[3]); - entities[1].ShouldNotBe(entities[4]); - entities[2].ShouldNotBe(entities[3]); - entities[2].ShouldNotBe(entities[4]); - entities[3].ShouldNotBe(entities[4]); + entities.ShouldBeUnique(); } [Fact] @@ -87,16 +78,7 @@ public void GivenListOfBuilders_WhenCallingBuildListImplicitly_ThenAListOfUnique { List entities = BasicCustomerBuilder.CreateListOfSize(5); - entities[0].ShouldNotBe(entities[1]); - entities[0].ShouldNotBe(entities[2]); - entities[0].ShouldNotBe(entities[3]); - entities[0].ShouldNotBe(entities[4]); - entities[1].ShouldNotBe(entities[2]); - entities[1].ShouldNotBe(entities[3]); - entities[1].ShouldNotBe(entities[4]); - entities[2].ShouldNotBe(entities[3]); - entities[2].ShouldNotBe(entities[4]); - entities[3].ShouldNotBe(entities[4]); + entities.ShouldBeUnique(); } [Fact] diff --git a/TestStack.Dossier.Tests/BuildTests.cs b/TestStack.Dossier.Tests/BuildTests.cs index 19242b3..f6088aa 100644 --- a/TestStack.Dossier.Tests/BuildTests.cs +++ b/TestStack.Dossier.Tests/BuildTests.cs @@ -1,6 +1,6 @@ using Shouldly; using TestStack.Dossier.Tests.Builders; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; using Xunit; namespace TestStack.Dossier.Tests diff --git a/TestStack.Dossier.Tests/BuilderBuildListTests.cs b/TestStack.Dossier.Tests/BuilderBuildListTests.cs new file mode 100644 index 0000000..458e112 --- /dev/null +++ b/TestStack.Dossier.Tests/BuilderBuildListTests.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Shouldly; +using TestStack.Dossier.DataSources.Generators; +using TestStack.Dossier.Lists; +using TestStack.Dossier.Tests.Builders; +using TestStack.Dossier.Tests.Stubs.ViewModels; +using Xunit; + +namespace TestStack.Dossier.Tests +{ + public class BuilderBuildListTests + { + private DateTime _enrollmentDate = new DateTime(2004, 9, 9); + + [Fact] + public void GivenANormalBuilderInstance_WhenCallingIsListBuilderProxy_ThenReturnFalse() + { + var builder = Builder.CreateNew(); + + builder.IsListBuilderProxy().ShouldBe(false); + } + + [Fact] + public void GivenAListBuilderProxyInstance_WhenCallingIsListBuilderProxy_ThenReturnTrue() + { + var builder = Builder.CreateListOfSize(1).TheFirst(1); + + builder.IsListBuilderProxy().ShouldBe(true); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListExplicitly_ThenAListOfEntitiesOfTheRightSizeShouldBeReturned() + { + var builders = Builder.CreateListOfSize(5); + + var entities = builders.BuildList(); + + entities.Count.ShouldBe(5); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListImplicitly_ThenAListOfEntitiesOfTheRightSizeShouldBeReturned() + { + List entities = Builder.CreateListOfSize(5); + + entities.Count.ShouldBe(5); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListExplicitly_ThenAListOfEntitiesOfTheRightTypeShouldBeReturned() + { + var builders = Builder.CreateListOfSize(5); + + var entities = builders.BuildList(); + + entities.ShouldBeAssignableTo>(); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListImplicitly_ThenAListOfEntitiesOfTheRightTypeShouldBeReturned() + { + List entities = Builder.CreateListOfSize(5); + + entities.ShouldBeAssignableTo>(); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListExplicitly_ThenAListOfUniqueEntitiesShouldBeReturned() + { + var builders = Builder.CreateListOfSize(5); + + var entities = builders.BuildList(); + + entities.ShouldBeUnique(); + } + + [Fact] + public void GivenListOfBuilders_WhenCallingBuildListImplicitly_ThenAListOfUniqueEntitiesShouldBeReturned() + { + List entities = Builder.CreateListOfSize(5); + + entities.ShouldBeUnique(); + } + + [Fact] + public void GivenListOfBuildersWithCustomisation_WhenBuildingEntitiesExplicitly_ThenTheCustomisationShouldTakeEffect() + { + var generator = new SequentialGenerator(0, 100); + var list = StudentViewModelBuilder.CreateListOfSize(3) + .All().With(b => b.WithFirstName(generator.Generate().ToString())); + + var data = list.BuildList(); + + data.Select(c => c.FirstName).ToArray() + .ShouldBe(new[] { "0", "1", "2" }); + } + + [Fact] + public void GivenListOfBuildersWithCustomisation_WhenBuildingEntitiesImplicitly_ThenTheCustomisationShouldTakeEffect() + { + var generator = new SequentialGenerator(0, 100); + + List data = StudentViewModelBuilder.CreateListOfSize(3) + .All().With(b => b.WithFirstName(generator.Generate().ToString())); + + data.Select(c => c.FirstName).ToArray() + .ShouldBe(new[] { "0", "1", "2" }); + } + + [Fact] + public void GivenListOfBuildersWithARangeOfCustomisationMethods_WhenBuildingEntitiesExplicitly_ThenThenTheListIsBuiltAndModifiedCorrectly() + { + var i = 0; + var studentViewModels = StudentViewModelBuilder.CreateListOfSize(5) + .TheFirst(1).WithFirstName("First") + .TheNext(1).WithLastName("Next Last") + .TheLast(1).WithLastName("Last Last") + .ThePrevious(2).With(b => b.WithLastName("last" + (++i).ToString())) + .All().WhoEntrolledIn(_enrollmentDate) + .BuildList(); + + studentViewModels.ShouldBeAssignableTo>(); + studentViewModels.Count.ShouldBe(5); + studentViewModels[0].FirstName.ShouldBe("First"); + studentViewModels[1].LastName.ShouldBe("Next Last"); + studentViewModels[2].LastName.ShouldBe("last1"); + studentViewModels[3].LastName.ShouldBe("last2"); + studentViewModels[4].LastName.ShouldBe("Last Last"); + studentViewModels.ShouldAllBe(c => c.EnrollmentDate == _enrollmentDate); + } + + [Fact] + public void GivenListOfBuildersWithARangeOfCustomisationMethods_WhenBuildingEntitiesImplicitly_ThenThenTheListIsBuiltAndModifiedCorrectly() + { + var i = 0; + List studentViewModels = StudentViewModelBuilder.CreateListOfSize(5) + .TheFirst(1).WithFirstName("First") + .TheNext(1).WithLastName("Next Last") + .TheLast(1).WithLastName("Last Last") + .ThePrevious(2).With(b => b.WithLastName("last" + (++i).ToString())) + .All().WhoEntrolledIn(_enrollmentDate); + + studentViewModels.ShouldBeAssignableTo>(); + studentViewModels.Count.ShouldBe(5); + studentViewModels[0].FirstName.ShouldBe("First"); + studentViewModels[1].LastName.ShouldBe("Next Last"); + studentViewModels[2].LastName.ShouldBe("last1"); + studentViewModels[3].LastName.ShouldBe("last2"); + studentViewModels[4].LastName.ShouldBe("Last Last"); + studentViewModels.ShouldAllBe(c => c.EnrollmentDate == _enrollmentDate); + } + + [Fact] + public void WhenBuildingEntitiesExplicitly_ThenTheAnonymousValueFixtureIsSharedAcrossBuilders() + { + var studentViewModels = StudentViewModelBuilder.CreateListOfSize(5).BuildList(); + + studentViewModels.Select(x => x.Grade).ShouldBeUnique(); + } + + [Fact] + public void WhenBuildingEntitiesImplicitly_ThenTheAnonymousValueFixtureIsSharedAcrossBuilders() + { + List studentViewModels = StudentViewModelBuilder.CreateListOfSize(5); + + studentViewModels.Select(x => x.Grade).ShouldBeUnique(); + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier.Tests/BuilderBuildTests.cs b/TestStack.Dossier.Tests/BuilderBuildTests.cs new file mode 100644 index 0000000..87530a8 --- /dev/null +++ b/TestStack.Dossier.Tests/BuilderBuildTests.cs @@ -0,0 +1,72 @@ +using System; +using Shouldly; +using TestStack.Dossier.Tests.Builders; +using TestStack.Dossier.Tests.Stubs.Entities; +using TestStack.Dossier.Tests.Stubs.ViewModels; +using Xunit; + +namespace TestStack.Dossier.Tests +{ + public class BuilderBuildTests + { + [Fact] + public void GivenBasicBuilder_WhenCallingBuildExplicitly_ThenReturnAnObject() + { + var builder = Builder.CreateNew(); + + var customer = builder.Build(); + + customer.ShouldBeOfType(); + } + + [Fact] + public void GivenBuilder_WhenCallingSetExplicitly_ShouldOverrideValues() + { + var builder = Builder.CreateNew() + .Set(x => x.FirstName, "Pi") + .Set(x => x.LastName, "Lanningham") + .Set(x => x.YearJoined, 2014); + + var customer = builder.Build(); + + customer.FirstName.ShouldBe("Pi"); + customer.LastName.ShouldBe("Lanningham"); + customer.YearJoined.ShouldBe(2014); + } + + [Fact] + public void GivenBasicBuilder_WhenCallingBuildImplicitly_ThenReturnAnObject() + { + Customer customer = Builder.CreateNew(); + + customer.ShouldBeOfType(); + } + + [Fact] + public void GivenBuilder_WhenCallingSetImplicitly_ShouldOverrideValues() + { + Customer customer = Builder.CreateNew() + .Set(x => x.FirstName, "Pi") + .Set(x => x.LastName, "Lanningham") + .Set(x => x.YearJoined, 2014); + + customer.FirstName.ShouldBe("Pi"); + customer.LastName.ShouldBe("Lanningham"); + customer.YearJoined.ShouldBe(2014); + } + + [Fact] + public void GivenBuilder_WhenBuildingObjectWithCtorAndPrivateSetters_ShouldSetPrivateSetters() + { + var id = Guid.NewGuid(); + InstructorViewModel instructor = Builder.CreateNew() + .Set(x => x.FirstName, "Pi") + .Set(x => x.LastName, "Lanningham") + .Set(x => x.Id, id); + + instructor.FirstName.ShouldBe("Pi"); + instructor.LastName.ShouldBe("Lanningham"); + instructor.Id.ShouldBe(id); + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier.Tests/Builders/AutoConstructorCustomerBuilder.cs b/TestStack.Dossier.Tests/Builders/AutoConstructorCustomerBuilder.cs index f1b7b09..70668ac 100644 --- a/TestStack.Dossier.Tests/Builders/AutoConstructorCustomerBuilder.cs +++ b/TestStack.Dossier.Tests/Builders/AutoConstructorCustomerBuilder.cs @@ -1,18 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Factories; +using TestStack.Dossier.Tests.Stubs.Entities; namespace TestStack.Dossier.Tests.Builders { class AutoConstructorCustomerBuilder : TestDataBuilder { - protected override Customer BuildObject() - { - return BuildByConstructor(); - } + public AutoConstructorCustomerBuilder() + : base(new ConstructorFactory()) { } public AutoConstructorCustomerBuilder WithFirstName(string firstName) { diff --git a/TestStack.Dossier.Tests/Builders/BasicCustomerBuilder.cs b/TestStack.Dossier.Tests/Builders/BasicCustomerBuilder.cs index df39128..dc6462f 100644 --- a/TestStack.Dossier.Tests/Builders/BasicCustomerBuilder.cs +++ b/TestStack.Dossier.Tests/Builders/BasicCustomerBuilder.cs @@ -1,6 +1,6 @@ using System; using System.Linq.Expressions; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; namespace TestStack.Dossier.Tests.Builders { diff --git a/TestStack.Dossier.Tests/Builders/CustomerBuilder.cs b/TestStack.Dossier.Tests/Builders/CustomerBuilder.cs index 96e133c..319a57c 100644 --- a/TestStack.Dossier.Tests/Builders/CustomerBuilder.cs +++ b/TestStack.Dossier.Tests/Builders/CustomerBuilder.cs @@ -1,4 +1,4 @@ -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; namespace TestStack.Dossier.Tests.Builders { diff --git a/TestStack.Dossier.Tests/Builders/ProxyAlteringCustomerBuilder.cs b/TestStack.Dossier.Tests/Builders/ProxyAlteringCustomerBuilder.cs index 9272dd4..2419fdf 100644 --- a/TestStack.Dossier.Tests/Builders/ProxyAlteringCustomerBuilder.cs +++ b/TestStack.Dossier.Tests/Builders/ProxyAlteringCustomerBuilder.cs @@ -1,6 +1,6 @@ using System; using NSubstitute; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; namespace TestStack.Dossier.Tests.Builders { diff --git a/TestStack.Dossier.Tests/Builders/StudentViewModelBuilder.cs b/TestStack.Dossier.Tests/Builders/StudentViewModelBuilder.cs new file mode 100644 index 0000000..9669a1d --- /dev/null +++ b/TestStack.Dossier.Tests/Builders/StudentViewModelBuilder.cs @@ -0,0 +1,23 @@ +using System; +using TestStack.Dossier.Tests.Stubs.ViewModels; + +namespace TestStack.Dossier.Tests.Builders +{ + public class StudentViewModelBuilder : TestDataBuilder + { + public virtual StudentViewModelBuilder WithFirstName(string firstName) + { + return Set(x => x.FirstName, firstName); + } + + public virtual StudentViewModelBuilder WithLastName(string lastName) + { + return Set(x => x.LastName, lastName); + } + + public virtual StudentViewModelBuilder WhoEntrolledIn(DateTime enrollmentDate) + { + return Set(x => x.EnrollmentDate, enrollmentDate); + } + } +} diff --git a/TestStack.Dossier.Tests/Factories/FactoryTests.cs b/TestStack.Dossier.Tests/Factories/FactoryTests.cs new file mode 100644 index 0000000..c90dc01 --- /dev/null +++ b/TestStack.Dossier.Tests/Factories/FactoryTests.cs @@ -0,0 +1,88 @@ +using System; +using Shouldly; +using TestStack.Dossier.Factories; +using TestStack.Dossier.Tests.Stubs.Entities; +using TestStack.Dossier.Tests.Stubs.ViewModels; +using Xunit; + +namespace TestStack.Dossier.Tests.Factories +{ + public class FactoryTests + { + [Fact] + public void GivenAllPropertiesFactory_WhenBuilding_ThenAllPropertiesSet() + { + InstructorViewModel instructor = Builder.CreateNew(new AllPropertiesFactory()); + + // ctor properties + instructor.Id.ShouldNotBe(Guid.Empty); + instructor.FirstName.ShouldNotBe(null); + instructor.LastName.ShouldNotBe(null); + + // public properties + instructor.Room.ShouldNotBe(null); + instructor.NumberOfStudents.ShouldNotBe(0); + + // private properties + instructor.Subject.ShouldNotBe(null); + instructor.YearsAtSchool.ShouldNotBe(0); + } + + [Fact] + public void GivenAutoFixtureFactory_WhenBuilding_ThenOnlyConstructorAndPublicPropertiesSet() + { + InstructorViewModel instructor = Builder.CreateNew(new AutoFixtureFactory()); + + // ctor properties + instructor.Id.ShouldNotBe(Guid.Empty); + instructor.FirstName.ShouldNotBe(null); + instructor.LastName.ShouldNotBe(null); + + // public properties + instructor.Room.ShouldNotBe(null); + instructor.NumberOfStudents.ShouldNotBe(0); + + // private properties + instructor.Subject.ShouldBe(null); + instructor.YearsAtSchool.ShouldBe(0); + } + + [Fact] + public void GivenConstructorPropertiesFactory_WhenBuilding_ThenOnlyConstructorPropertiesSet() + { + InstructorViewModel instructor = Builder.CreateNew(new ConstructorFactory()); + + // ctor properties + instructor.Id.ShouldNotBe(Guid.Empty); + instructor.FirstName.ShouldNotBe(null); + instructor.LastName.ShouldNotBe(null); + + // public properties + instructor.Room.ShouldBe(null); + instructor.NumberOfStudents.ShouldBe(0); + + // private properties + instructor.Subject.ShouldBe(null); + instructor.YearsAtSchool.ShouldBe(0); + } + + [Fact] + public void GivenPublicPropertiesFactory_WhenBuilding_ThenOnlyConstructorAndPublicPropertiesSet() + { + InstructorViewModel instructor = Builder.CreateNew(new PublicPropertiesFactory()); + + // ctor properties + instructor.Id.ShouldNotBe(Guid.Empty); + instructor.FirstName.ShouldNotBe(null); + instructor.LastName.ShouldNotBe(null); + + // public properties + instructor.Room.ShouldNotBe(null); + instructor.NumberOfStudents.ShouldBeGreaterThan(0); + + // private properties + instructor.Subject.ShouldBe(null); + instructor.YearsAtSchool.ShouldBe(0); + } + } +} diff --git a/TestStack.Dossier.Tests/GetAnonymousTests.cs b/TestStack.Dossier.Tests/GetAnonymousTests.cs index 442b556..c415c73 100644 --- a/TestStack.Dossier.Tests/GetAnonymousTests.cs +++ b/TestStack.Dossier.Tests/GetAnonymousTests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System; +using Shouldly; using TestStack.Dossier.DataSources.Person; using TestStack.Dossier.Lists; using TestStack.Dossier.Tests.Builders; @@ -113,9 +114,20 @@ public bool CanSupplyValue(string propertyName) && propertyName.ToLower().StartsWith("year"); } + public bool CanSupplyValue(Type type, string propertyName) + { + return type == typeof(int) + && propertyName.ToLower().StartsWith("year"); + } + public TValue GenerateAnonymousValue(AnonymousValueFixture any, string propertyName) { return (TValue)(object)_year++; } + + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return _year++; + } } } diff --git a/TestStack.Dossier.Tests/ProxyBuilderTests.cs b/TestStack.Dossier.Tests/ProxyBuilderTests.cs index e7aa540..ae9aef8 100644 --- a/TestStack.Dossier.Tests/ProxyBuilderTests.cs +++ b/TestStack.Dossier.Tests/ProxyBuilderTests.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using NSubstitute; using Shouldly; -using TestStack.Dossier.Tests.Entities; +using TestStack.Dossier.Tests.Stubs.Entities; using Xunit; namespace TestStack.Dossier.Tests diff --git a/TestStack.Dossier.Tests/Entities/Company.cs b/TestStack.Dossier.Tests/Stubs/Entities/Company.cs similarity index 86% rename from TestStack.Dossier.Tests/Entities/Company.cs rename to TestStack.Dossier.Tests/Stubs/Entities/Company.cs index 4e2dfbe..2635100 100644 --- a/TestStack.Dossier.Tests/Entities/Company.cs +++ b/TestStack.Dossier.Tests/Stubs/Entities/Company.cs @@ -1,4 +1,4 @@ -namespace TestStack.Dossier.Tests.Entities +namespace TestStack.Dossier.Tests.Stubs.Entities { public class Company { diff --git a/TestStack.Dossier.Tests/Entities/Customer.cs b/TestStack.Dossier.Tests/Stubs/Entities/Customer.cs similarity index 95% rename from TestStack.Dossier.Tests/Entities/Customer.cs rename to TestStack.Dossier.Tests/Stubs/Entities/Customer.cs index 5796b0d..0e71270 100644 --- a/TestStack.Dossier.Tests/Entities/Customer.cs +++ b/TestStack.Dossier.Tests/Stubs/Entities/Customer.cs @@ -1,6 +1,6 @@ -using System; +using System; -namespace TestStack.Dossier.Tests.Entities +namespace TestStack.Dossier.Tests.Stubs.Entities { public class Customer { diff --git a/TestStack.Dossier.Tests/Entities/CustomerClass.cs b/TestStack.Dossier.Tests/Stubs/Entities/CustomerClass.cs similarity index 70% rename from TestStack.Dossier.Tests/Entities/CustomerClass.cs rename to TestStack.Dossier.Tests/Stubs/Entities/CustomerClass.cs index 81f3956..dd79d7b 100644 --- a/TestStack.Dossier.Tests/Entities/CustomerClass.cs +++ b/TestStack.Dossier.Tests/Stubs/Entities/CustomerClass.cs @@ -1,4 +1,4 @@ -namespace TestStack.Dossier.Tests.Entities +namespace TestStack.Dossier.Tests.Stubs.Entities { public enum CustomerClass { diff --git a/TestStack.Dossier.Tests/Stubs/ViewModels/Grade.cs b/TestStack.Dossier.Tests/Stubs/ViewModels/Grade.cs new file mode 100644 index 0000000..8598bb2 --- /dev/null +++ b/TestStack.Dossier.Tests/Stubs/ViewModels/Grade.cs @@ -0,0 +1,7 @@ +namespace TestStack.Dossier.Tests.Stubs.ViewModels +{ + public enum Grade + { + A, B, C, D, F + } +} \ No newline at end of file diff --git a/TestStack.Dossier.Tests/Stubs/ViewModels/InstructorViewModel.cs b/TestStack.Dossier.Tests/Stubs/ViewModels/InstructorViewModel.cs new file mode 100644 index 0000000..b0db3e9 --- /dev/null +++ b/TestStack.Dossier.Tests/Stubs/ViewModels/InstructorViewModel.cs @@ -0,0 +1,32 @@ +using System; + +namespace TestStack.Dossier.Tests.Stubs.ViewModels +{ + public class InstructorViewModel + { + public InstructorViewModel(Guid id, string firstName, string lastName) + { + Id = id; + FirstName = firstName; + LastName = lastName; + } + + public Guid Id { get; private set; } + + public string LastName { get; set; } + public string FirstName { get; private set; } + + public string FullName + { + get + { + return FirstName + " " + LastName; + } + } + public string Room{ get; set; } + + public string Subject{ get; private set; } + public int YearsAtSchool { get; private set; } + public int NumberOfStudents { get; set; } + } +} diff --git a/TestStack.Dossier.Tests/Stubs/ViewModels/StudentViewModel.cs b/TestStack.Dossier.Tests/Stubs/ViewModels/StudentViewModel.cs new file mode 100644 index 0000000..674a844 --- /dev/null +++ b/TestStack.Dossier.Tests/Stubs/ViewModels/StudentViewModel.cs @@ -0,0 +1,35 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace TestStack.Dossier.Tests.Stubs.ViewModels +{ + public class StudentViewModel + { + public int Id { get; set; } + + [Required] + [StringLength(50)] + [Display(Name = "Last Name")] + public string LastName { get; set; } + [Required] + [StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")] + [Display(Name = "First Name")] + public string FirstName { get; set; } + + [Display(Name = "Full Name")] + public string FullName + { + get + { + return FirstName + " " + LastName; + } + } + + [DataType(DataType.Date)] + [DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)] + [Display(Name = "Enrollment Date")] + public DateTime EnrollmentDate { get; set; } + + public Grade Grade { get; set; } + } +} diff --git a/TestStack.Dossier.Tests/TestHelpers/StaticAnonymousValueSupplier.cs b/TestStack.Dossier.Tests/TestHelpers/StaticAnonymousValueSupplier.cs index dc3db91..4c7c9a0 100644 --- a/TestStack.Dossier.Tests/TestHelpers/StaticAnonymousValueSupplier.cs +++ b/TestStack.Dossier.Tests/TestHelpers/StaticAnonymousValueSupplier.cs @@ -1,4 +1,6 @@ -namespace TestStack.Dossier.Tests.TestHelpers +using System; + +namespace TestStack.Dossier.Tests.TestHelpers { public class StaticAnonymousValueSupplier : IAnonymousValueSupplier { @@ -14,9 +16,19 @@ public bool CanSupplyValue(string propertyName) return typeof(TValue) == _valueToSupply.GetType(); } + public bool CanSupplyValue(Type type, string propertyName) + { + return type == _valueToSupply.GetType(); + } + public TValue GenerateAnonymousValue(AnonymousValueFixture any, string propertyName) { return (TValue) _valueToSupply; } + + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return _valueToSupply; + } } } diff --git a/TestStack.Dossier.Tests/TestStack.Dossier.Tests.csproj b/TestStack.Dossier.Tests/TestStack.Dossier.Tests.csproj index 97b84cd..0348725 100644 --- a/TestStack.Dossier.Tests/TestStack.Dossier.Tests.csproj +++ b/TestStack.Dossier.Tests/TestStack.Dossier.Tests.csproj @@ -44,6 +44,7 @@ ..\packages\Shouldly.2.4.0\lib\net40\Shouldly.dll + @@ -54,9 +55,15 @@ + + + - + + + + @@ -76,13 +83,14 @@ - - + + + diff --git a/TestStack.Dossier/AnonymousValueFixture.cs b/TestStack.Dossier/AnonymousValueFixture.cs index 64e193e..9402247 100644 --- a/TestStack.Dossier/AnonymousValueFixture.cs +++ b/TestStack.Dossier/AnonymousValueFixture.cs @@ -82,13 +82,23 @@ public AnonymousValueFixture() /// The anonymous value, taking into account any registered conventions public T Get(Expression> property) { - var propertyName = PropertyNameGetter.Get(property); + var propertyName = Reflector.GetPropertyNameFor(property); var valueSupplier = LocalValueSuppliers .Concat(GlobalValueSuppliers) .Concat(DefaultValueSuppliers) - .First(s => s.CanSupplyValue(propertyName)); + .First(s => s.CanSupplyValue(typeof(T), propertyName)); return valueSupplier.GenerateAnonymousValue(this, propertyName); } + + public object Get(Type type, string propertyName) + { + var valueSupplier = LocalValueSuppliers + .Concat(GlobalValueSuppliers) + .Concat(DefaultValueSuppliers) + .First(s => s.CanSupplyValue(type,propertyName)); + + return valueSupplier.GenerateAnonymousValue(this, type, propertyName); + } } } diff --git a/TestStack.Dossier/Builder.cs b/TestStack.Dossier/Builder.cs new file mode 100644 index 0000000..08d9f49 --- /dev/null +++ b/TestStack.Dossier/Builder.cs @@ -0,0 +1,25 @@ +using TestStack.Dossier.Factories; + +namespace TestStack.Dossier +{ + /// + /// A stand-alone class for building objects on the fly. + /// + /// The type of object this class generates. + public class Builder : TestDataBuilder> + where T : class + { + /// + /// Initialises a new Builder. + /// + /// Returns a new instance of a Builder for the type of T + public static Builder CreateNew(IFactory factory = null) + { + if (factory == null) + { + factory = new AllPropertiesFactory(); + } + return new Builder {Factory = factory}; + } + } +} diff --git a/TestStack.Dossier/Factories/AllPropertiesFactory.cs b/TestStack.Dossier/Factories/AllPropertiesFactory.cs new file mode 100644 index 0000000..34bbc6d --- /dev/null +++ b/TestStack.Dossier/Factories/AllPropertiesFactory.cs @@ -0,0 +1,30 @@ +using Ploeh.AutoFixture; + +namespace TestStack.Dossier.Factories +{ + /// + /// Creates an instance of an object by setting all public and private properties. + /// + public class AllPropertiesFactory : IFactory + { + /// + public TObject BuildObject(TestDataBuilder builder) + where TObject : class + where TBuilder : TestDataBuilder, new() + { + var model = builder.Any.Fixture.Create(); + + var properties = Reflector.GetSettablePropertiesFor(); + foreach (var property in properties) + { + if (property.CanWrite) + { + var val = builder.Get(property.PropertyType, property.Name); + property.SetValue(model, val, null); + } + } + + return model; + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier/Factories/AutoFixtureFactory.cs b/TestStack.Dossier/Factories/AutoFixtureFactory.cs new file mode 100644 index 0000000..831c65b --- /dev/null +++ b/TestStack.Dossier/Factories/AutoFixtureFactory.cs @@ -0,0 +1,18 @@ +using Ploeh.AutoFixture; + +namespace TestStack.Dossier.Factories +{ + /// + /// Creates an instance of an object with AutoFixture. + /// + public class AutoFixtureFactory : IFactory + { + /// + public TObject BuildObject(TestDataBuilder builder) + where TObject : class + where TBuilder : TestDataBuilder, new() + { + return builder.Any.Fixture.Create(); + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier/Factories/ConstructorFactory.cs b/TestStack.Dossier/Factories/ConstructorFactory.cs new file mode 100644 index 0000000..391aa2c --- /dev/null +++ b/TestStack.Dossier/Factories/ConstructorFactory.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Ploeh.AutoFixture; + +namespace TestStack.Dossier.Factories +{ + /// + /// Builds the object using the constructor with the most arguments. + /// + public class ConstructorFactory : IFactory + { + /// + public virtual TObject BuildObject(TestDataBuilder builder) + where TObject : class + where TBuilder : TestDataBuilder, new() + { + var longestConstructor = typeof(TObject) + .GetConstructors() + .OrderByDescending(x => x.GetParameters().Length) + .FirstOrDefault(); + + if (longestConstructor == null) throw new ObjectCreationException(); + + var parameterValues = longestConstructor + .GetParameters() + .Select(x => CallGetWithType(x.Name, x.ParameterType, typeof(TObject), typeof(TBuilder), builder)); + + return (TObject)longestConstructor.Invoke(parameterValues.ToArray()); + } + + private static object CallGetWithType(string propertyName, Type propertyType, Type objectType, Type builderType, object builder) + { + // Make a Func + var expressionDelegateType = typeof(Func<,>).MakeGenericType(objectType, propertyType); + var tObjParameterType = Expression.Parameter(objectType); + + var closedGenericGetMethod = builderType + .GetMethods() + .First(IsGenericGetMethod()) + .MakeGenericMethod(propertyType); + var valueStoredInBuilder = closedGenericGetMethod.Invoke(builder, new object[] + { + Expression.Lambda( + expressionDelegateType, + Expression.Property(tObjParameterType, propertyName), + tObjParameterType) + }); + + return valueStoredInBuilder; + } + + private static Func IsGenericGetMethod() + { + return method => method.Name == "Get" && method.ContainsGenericParameters && method.GetGenericArguments().Length == 1; + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier/Factories/IFactory.cs b/TestStack.Dossier/Factories/IFactory.cs new file mode 100644 index 0000000..d320f26 --- /dev/null +++ b/TestStack.Dossier/Factories/IFactory.cs @@ -0,0 +1,19 @@ +namespace TestStack.Dossier.Factories +{ + /// + /// Interface for object building strategies + /// + public interface IFactory + { + /// + /// Takes a builder and generates an object of the specified type. + /// + /// An instance of the TestDataBuilder. + /// The generic type of the object that will be generated. + /// The generic type of the TestDataBuilder. + /// An instance of the created object. + TObject BuildObject(TestDataBuilder builder) + where TObject : class + where TBuilder : TestDataBuilder, new(); + } +} \ No newline at end of file diff --git a/TestStack.Dossier/Factories/PublicPropertiesFactory.cs b/TestStack.Dossier/Factories/PublicPropertiesFactory.cs new file mode 100644 index 0000000..4af57f3 --- /dev/null +++ b/TestStack.Dossier/Factories/PublicPropertiesFactory.cs @@ -0,0 +1,33 @@ +using System.Reflection; + +namespace TestStack.Dossier.Factories +{ + /// + /// Creates an instance of an object by setting all public properties but not private properties. + /// + public class PublicPropertiesFactory : ConstructorFactory + { + /// + public override TObject BuildObject(TestDataBuilder builder) + { + var model = base.BuildObject(builder); + + var properties = Reflector.GetSettablePropertiesFor(); + foreach (var property in properties) + { + if (PropertySetterIsPublic(property)) + { + var val = builder.Get(property.PropertyType, property.Name); + property.SetValue(model, val, null); + } + } + + return model; + } + + private static bool PropertySetterIsPublic(PropertyInfo property) + { + return property.CanWrite && property.GetSetMethod() != null; + } + } +} \ No newline at end of file diff --git a/TestStack.Dossier/IAnonymousValueSupplier.cs b/TestStack.Dossier/IAnonymousValueSupplier.cs index eae1839..9eda5af 100644 --- a/TestStack.Dossier/IAnonymousValueSupplier.cs +++ b/TestStack.Dossier/IAnonymousValueSupplier.cs @@ -1,4 +1,6 @@ -namespace TestStack.Dossier +using System; + +namespace TestStack.Dossier { /// /// Inheritors can supply an anonymous value. @@ -8,11 +10,10 @@ public interface IAnonymousValueSupplier /// /// Returns whether or not this supplier can supply an anonymous value for the given property. /// - /// The type that the property is enclosed in - /// The type of the target property - the anonymous value will need to be of this type + /// The type of the property to generate a value for /// The name of the property to generate a value for /// Whether or not this supplier can supply an anonymous value - bool CanSupplyValue(string propertyName); + bool CanSupplyValue(Type type, string propertyName); /// /// Return an anonymous value for the given property and fixture. @@ -23,5 +24,14 @@ public interface IAnonymousValueSupplier /// The name of the property to return an anonymous value for /// The anonymous value TValue GenerateAnonymousValue(AnonymousValueFixture any, string propertyName); + + /// + /// Return an anonymous value for the given property and fixture. + /// + /// The type that the property is enclosed in + /// Anonymous value fixture + /// The name of the property to return an anonymous value for + /// The anonymous value + object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName); } } diff --git a/TestStack.Dossier/ProxyBuilder.cs b/TestStack.Dossier/ProxyBuilder.cs index 306f79d..5a7ff05 100644 --- a/TestStack.Dossier/ProxyBuilder.cs +++ b/TestStack.Dossier/ProxyBuilder.cs @@ -29,7 +29,7 @@ public ProxyBuilder(Dictionary properties) public T Build() { var proxy = Substitute.For(); - var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + var properties = Reflector.GetSettablePropertiesFor(); foreach (var property in properties.Where(property => _properties.ContainsKey(property.Name))) { if (property.GetGetMethod().IsVirtual) diff --git a/TestStack.Dossier/PropertyNameGetter.cs b/TestStack.Dossier/Reflector.cs similarity index 61% rename from TestStack.Dossier/PropertyNameGetter.cs rename to TestStack.Dossier/Reflector.cs index 2270e16..5e3f51c 100644 --- a/TestStack.Dossier/PropertyNameGetter.cs +++ b/TestStack.Dossier/Reflector.cs @@ -1,11 +1,12 @@ using System; using System.Linq.Expressions; +using System.Reflection; namespace TestStack.Dossier { - internal static class PropertyNameGetter + internal static class Reflector { - public static string Get(Expression> property) + public static string GetPropertyNameFor(Expression> property) { var memExp = property.Body as MemberExpression; if (memExp == null) @@ -20,5 +21,10 @@ public static string Get(Expression> prop return memExp.Member.Name; } + + public static PropertyInfo[] GetSettablePropertiesFor() + { + return typeof (TObject).GetProperties(BindingFlags.Public | BindingFlags.Instance); + } } } diff --git a/TestStack.Dossier/Suppliers/DefaultEmailValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultEmailValueSupplier.cs index 2ea3b4a..754faba 100644 --- a/TestStack.Dossier/Suppliers/DefaultEmailValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultEmailValueSupplier.cs @@ -1,4 +1,5 @@ -using TestStack.Dossier.EquivalenceClasses.Person; +using System; +using TestStack.Dossier.EquivalenceClasses.Person; namespace TestStack.Dossier.Suppliers { @@ -8,9 +9,9 @@ namespace TestStack.Dossier.Suppliers public class DefaultEmailValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { - return typeof (TValue) == typeof(string) && propertyName.ToLower().Contains("email"); + return type == typeof(string) && propertyName.ToLower().Contains("email"); } /// @@ -18,5 +19,11 @@ public TValue GenerateAnonymousValue(AnonymousValueFixture any, { return (TValue) (object) any.EmailAddress(); } + + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return any.EmailAddress(); + } } } diff --git a/TestStack.Dossier/Suppliers/DefaultFirstNameValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultFirstNameValueSupplier.cs index 10251ca..bbd2e59 100644 --- a/TestStack.Dossier/Suppliers/DefaultFirstNameValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultFirstNameValueSupplier.cs @@ -1,4 +1,5 @@ -using TestStack.Dossier.EquivalenceClasses.Person; +using System; +using TestStack.Dossier.EquivalenceClasses.Person; namespace TestStack.Dossier.Suppliers { @@ -8,9 +9,9 @@ namespace TestStack.Dossier.Suppliers public class DefaultFirstNameValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { - return typeof (TValue) == typeof(string) && propertyName.ToLower() == "firstname"; + return type == typeof(string) && propertyName.ToLower() == "firstname"; } /// @@ -18,5 +19,11 @@ public TValue GenerateAnonymousValue(AnonymousValueFixture any, { return (TValue) (object) any.FirstName(); } + + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return any.FirstName(); + } } } diff --git a/TestStack.Dossier/Suppliers/DefaultLastNameValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultLastNameValueSupplier.cs index 522bd4e..e0fc9c7 100644 --- a/TestStack.Dossier/Suppliers/DefaultLastNameValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultLastNameValueSupplier.cs @@ -1,4 +1,5 @@ -using TestStack.Dossier.EquivalenceClasses.Person; +using System; +using TestStack.Dossier.EquivalenceClasses.Person; namespace TestStack.Dossier.Suppliers { @@ -8,16 +9,22 @@ namespace TestStack.Dossier.Suppliers public class DefaultLastNameValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { - return typeof (TValue) == typeof(string) && + return type == typeof(string) && (propertyName.ToLower() == "lastname" || propertyName.ToLower() == "surname"); } /// public TValue GenerateAnonymousValue(AnonymousValueFixture any, string propertyName) { - return (TValue) (object) any.LastName(); + return (TValue)(object)any.LastName(); + } + + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return any.LastName(); } } } diff --git a/TestStack.Dossier/Suppliers/DefaultStringValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultStringValueSupplier.cs index f63b4a3..ed5b40f 100644 --- a/TestStack.Dossier/Suppliers/DefaultStringValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultStringValueSupplier.cs @@ -1,4 +1,5 @@ -using TestStack.Dossier.EquivalenceClasses; +using System; +using TestStack.Dossier.EquivalenceClasses; namespace TestStack.Dossier.Suppliers { @@ -8,9 +9,9 @@ namespace TestStack.Dossier.Suppliers public class DefaultStringValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { - return typeof (TValue) == typeof(string); + return type == typeof(string); } /// @@ -18,5 +19,11 @@ public TValue GenerateAnonymousValue(AnonymousValueFixture any, { return (TValue) (object) any.StringStartingWith(propertyName); } + + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + return any.StringStartingWith(propertyName); + } } } diff --git a/TestStack.Dossier/Suppliers/DefaultValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultValueSupplier.cs index bd1ed89..6bd6099 100644 --- a/TestStack.Dossier/Suppliers/DefaultValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultValueSupplier.cs @@ -1,4 +1,6 @@ -namespace TestStack.Dossier.Suppliers +using System; + +namespace TestStack.Dossier.Suppliers { /// /// Supplies default value for any type. @@ -6,7 +8,7 @@ public class DefaultValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { return true; } @@ -16,5 +18,16 @@ public TValue GenerateAnonymousValue(AnonymousValueFixture any, { return default(TValue); } + + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + // See stackoverflow: http://stackoverflow.com/questions/325426/programmatic-equivalent-of-defaulttype + if (type.IsValueType) + { + return Activator.CreateInstance(type); + } + return null; + } } } diff --git a/TestStack.Dossier/Suppliers/DefaultValueTypeValueSupplier.cs b/TestStack.Dossier/Suppliers/DefaultValueTypeValueSupplier.cs index 4495ffa..bf1cd68 100644 --- a/TestStack.Dossier/Suppliers/DefaultValueTypeValueSupplier.cs +++ b/TestStack.Dossier/Suppliers/DefaultValueTypeValueSupplier.cs @@ -1,4 +1,7 @@ -using Ploeh.AutoFixture; +using System; +using System.Linq; +using Ploeh.AutoFixture; +using Ploeh.AutoFixture.Kernel; namespace TestStack.Dossier.Suppliers { @@ -8,9 +11,9 @@ namespace TestStack.Dossier.Suppliers public class DefaultValueTypeValueSupplier : IAnonymousValueSupplier { /// - public bool CanSupplyValue(string propertyName) + public bool CanSupplyValue(Type type, string propertyName) { - return typeof (TValue).IsValueType; + return type.IsValueType; } /// @@ -18,5 +21,15 @@ public TValue GenerateAnonymousValue(AnonymousValueFixture any, { return any.Fixture.Create(); } + + /// How to create weakly-typed CreateAnonymous with AutoFixture + /// http://autofixture.codeplex.com/workitem/4229 + /// + public object GenerateAnonymousValue(AnonymousValueFixture any, Type type, string propertyName) + { + var context = new SpecimenContext(any.Fixture); + var specimen = context.Resolve(type); + return specimen; + } } } diff --git a/TestStack.Dossier/TestDataBuilder.cs b/TestStack.Dossier/TestDataBuilder.cs index 5b03bf1..c918f5a 100644 --- a/TestStack.Dossier/TestDataBuilder.cs +++ b/TestStack.Dossier/TestDataBuilder.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using Ploeh.AutoFixture; +using TestStack.Dossier.Factories; using TestStack.Dossier.Lists; namespace TestStack.Dossier @@ -19,6 +18,11 @@ public abstract class TestDataBuilder private readonly Dictionary _properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); private ProxyBuilder _proxyBuilder; + /// + /// The factory used to create the instance of TObject. + /// + public IFactory Factory; + /// /// The list builder instance (if this ia a list builder proxy). /// @@ -28,7 +32,16 @@ public abstract class TestDataBuilder /// Default Constructor. /// protected TestDataBuilder() + : this(new AllPropertiesFactory()) { + } + + /// + /// Allow object builder factory to be passed in + /// + protected TestDataBuilder(IFactory factory) + { + Factory = factory; Any = new AnonymousValueFixture(); } @@ -72,10 +85,15 @@ public static implicit operator List(TestDataBuilder } /// - /// Build the actual object - override this and call the constructor and any other methods. + /// Build the actual object - you can optionally override this and call the constructor and any other methods. /// /// The built object - protected abstract TObject BuildObject(); + protected virtual TObject BuildObject() + { + var model = Factory.BuildObject(this); + + return model; + } /// /// Return an NSubstitute proxy object when .Build() is called rather than a real object. @@ -102,7 +120,7 @@ protected virtual void AlterProxy(TObject proxy) {} /// The value to record public TBuilder Set(Expression> property, TValue value) { - _properties[PropertyNameGetter.Get(property)] = value; + _properties[Reflector.GetPropertyNameFor(property)] = value; return this as TBuilder; } @@ -110,7 +128,7 @@ public TBuilder Set(Expression> property, TValue v /// Gets the recorded value for the given property from {TObject} or an anonymous /// value if there isn't one specified. /// - /// The type of the property + /// The type of the property. /// A lambda expression specifying the property to retrieve the recorded value for /// The recorded value of the property or an anonymous value for it public TValue Get(Expression> property) @@ -118,9 +136,23 @@ public TValue Get(Expression> property) if (!Has(property)) return Any.Get(property); - return (TValue) _properties[PropertyNameGetter.Get(property)]; + return (TValue)_properties[Reflector.GetPropertyNameFor(property)]; } + /// + /// Gets the recorded value for the given property from {type} or an anonymous + /// value if there isn't one specified. + /// + /// The type of the property. + /// The property name. + /// + public object Get(Type type, string propertyName) + { + if (!Has(propertyName)) + return Any.Get(type, propertyName); + return _properties[propertyName]; + } + /// /// Gets the recorded value for the given property from {TObject} or if no /// value has been recorded the default value for {TValue}. @@ -155,7 +187,17 @@ public static ListBuilder CreateListOfSize(int size) /// Whether or not there is a recorded value for the property protected bool Has(Expression> property) { - return _properties.ContainsKey(PropertyNameGetter.Get(property)); + return Has(Reflector.GetPropertyNameFor(property)); + } + + /// + /// Returns whether or not there is currently an explicit value recorded against the given property from {TObject}. + /// + /// A string specifying the name of the property to retrieve the recorded value for + /// Whether or not there is a recorded value for the property + protected bool Has(string propertyName) + { + return _properties.ContainsKey(propertyName); } /// @@ -183,47 +225,5 @@ protected virtual TChildBuilder GetChildBuilder(Fun modifier(childBuilder); return childBuilder; } - - /// - /// Builds the object using the constructor with the most arguments. - /// - /// - protected TObject BuildByConstructor() - { - var longestConstructor = typeof (TObject) - .GetConstructors() - .OrderByDescending(x => x.GetParameters().Length) - .FirstOrDefault(); - - if (longestConstructor == null) throw new ObjectCreationException(); - - var parameterValues = longestConstructor - .GetParameters() - .Select(x => CallGetWithType(x.Name, x.ParameterType)); - - return (TObject) longestConstructor.Invoke(parameterValues.ToArray()); - } - - private object CallGetWithType(string propertyName, Type propertyType) - { - // Make a Func - var expressionDelegateType = typeof(Func<,>).MakeGenericType(typeof(TObject), propertyType); - - // Make an expression parameter of type TObj - var tObjParameterType = Expression.Parameter(typeof(TObject)); - - var valueStoredInBuilder = typeof(TBuilder) - .GetMethod("Get") - .MakeGenericMethod(propertyType) - .Invoke(this, new object[] - { - Expression.Lambda( - expressionDelegateType, - Expression.Property(tObjParameterType, propertyName), - tObjParameterType) - }); - - return valueStoredInBuilder; - } } } diff --git a/TestStack.Dossier/TestStack.Dossier.csproj b/TestStack.Dossier/TestStack.Dossier.csproj index 607d3e4..f00abcf 100644 --- a/TestStack.Dossier/TestStack.Dossier.csproj +++ b/TestStack.Dossier/TestStack.Dossier.csproj @@ -51,6 +51,7 @@ + @@ -79,6 +80,10 @@ + + + + @@ -86,7 +91,8 @@ - + +