From bf636b8adba7e68c213c3e13fa6c50e12a9b0e9a Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Mon, 18 May 2015 12:20:11 -0400 Subject: [PATCH 1/5] Adds a Set overload which takes a lambda factory method - The specific use case for this is that we wanted to be able to use equivalence classes. However, if you just use them naively, it would generate a single value and use it for all of the instances it created. - The lambda overload not only enables this case, but gives some power to constructing incrementing counters and the like. For example, using a Queue to generate an ordered set of names, or other such use cases. --- TestStack.Dossier.Tests/BuildTests.cs | 29 +++++++++++++++++++++- TestStack.Dossier/TestDataBuilder.cs | 35 +++++++++++++++++++-------- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/TestStack.Dossier.Tests/BuildTests.cs b/TestStack.Dossier.Tests/BuildTests.cs index 9c6d1d4..010376a 100644 --- a/TestStack.Dossier.Tests/BuildTests.cs +++ b/TestStack.Dossier.Tests/BuildTests.cs @@ -1,4 +1,5 @@ -using Shouldly; +using System.Collections.Generic; +using Shouldly; using TestStack.Dossier.Tests.TestHelpers.Builders; using TestStack.Dossier.Tests.TestHelpers.Objects.Entities; using Xunit; @@ -47,6 +48,32 @@ public void GivenBuilder_WhenCallingSetExplicitly_ShouldOverrideValues() customer.YearJoined.ShouldBe(2014); } + [Fact] + public void GivenBuilder_WhenCallingSetWithLambda_ShouldInvokeEachTime() + { + int counter = 2014; + var builder = new CustomerBuilder() + .Set(x => x.FirstName, "Pi") + .Set(x => x.LastName, "Lanningham") + .Set(x => x.YearJoined, () => counter++); + + var customerA = builder.Build(); + var customerB = builder.Build(); + + customerA.YearJoined.ShouldBe(2014); + customerB.YearJoined.ShouldBe(2015); + + List customerList = CustomerBuilder.CreateListOfSize(10) + .All() + .Set(x => x.YearJoined, () => counter++); + int newCounter = 2016; + foreach (var c in customerList) + { + c.YearJoined.ShouldBe(newCounter++); + } + + } + [Fact] public void GivenBasicBuilder_WhenCallingBuildImplicitly_ThenReturnAnObject() { diff --git a/TestStack.Dossier/TestDataBuilder.cs b/TestStack.Dossier/TestDataBuilder.cs index 50e1167..c3fb0d3 100644 --- a/TestStack.Dossier/TestDataBuilder.cs +++ b/TestStack.Dossier/TestDataBuilder.cs @@ -16,6 +16,7 @@ public abstract class TestDataBuilder where TBuilder : TestDataBuilder, new() { private readonly Dictionary _properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); + private readonly Dictionary> _propFactories = new Dictionary>(); private ProxyBuilder _proxyBuilder; /// @@ -107,19 +108,32 @@ public TBuilder AsProxy() /// /// The proxy object protected virtual void AlterProxy(TObject proxy) {} - + /// /// Records the given value for the given property from {TObject} and returns the builder to allow chaining. /// /// The type of the property /// A lambda expression specifying the property to record a value for - /// The builder so that other method calls can be chained + /// The value to set the property to + /// The builder so that other method calls can be chained public virtual TBuilder Set(Expression> property, TValue value) { _properties[Reflector.GetPropertyNameFor(property)] = value; return this as TBuilder; } + /// + /// Records a given value provider for the given property from {TObject} and returns the builder to allow chaining. + /// + /// The type of the property + /// A lambda expression specifying the property to record a value for + /// A method which produces instances of {TValue} for the property. + public virtual TBuilder Set(Expression> property, Func factory) + { + _propFactories[Reflector.GetPropertyNameFor(property)] = () => factory() as object; + return this as TBuilder; + } + /// /// Gets the recorded value for the given property from {TObject} or an anonymous /// value if there isn't one specified. @@ -129,10 +143,7 @@ public virtual TBuilder Set(Expression> property, /// The recorded value of the property or an anonymous value for it public TValue Get(Expression> property) { - if (!Has(property)) - return Any.Get(property); - - return (TValue)_properties[Reflector.GetPropertyNameFor(property)]; + return (TValue)Get(typeof (TValue), Reflector.GetPropertyNameFor(property)); } /// @@ -144,9 +155,13 @@ public TValue Get(Expression> property) /// public object Get(Type type, string propertyName) { - if (!Has(propertyName)) - return Any.Get(type, propertyName); - return _properties[propertyName]; + object value; + if (_properties.TryGetValue(propertyName, out value)) return value; + + Func factory; + if (_propFactories.TryGetValue(propertyName, out factory)) return factory(); + + return Any.Get(type, propertyName); } /// @@ -193,7 +208,7 @@ protected bool Has(Expression> property) /// Whether or not there is a recorded value for the property protected bool Has(string propertyName) { - return _properties.ContainsKey(propertyName); + return _properties.ContainsKey(propertyName) || _propFactories.ContainsKey(propertyName); } /// From 6eaee0bb2549e098408ab69c333210dd000d5970 Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Wed, 20 May 2015 19:50:27 -0400 Subject: [PATCH 2/5] Fixes up the set lambda, the function pattern now underlies other methods - It's a lot cleaner to use the factory lambdas as just constant functions for the other Set method. --- TestStack.Dossier.Tests/ProxyBuilderTests.cs | 25 ++++++++++---------- TestStack.Dossier/ProxyBuilder.cs | 9 +++---- TestStack.Dossier/TestDataBuilder.cs | 14 ++++------- 3 files changed, 23 insertions(+), 25 deletions(-) diff --git a/TestStack.Dossier.Tests/ProxyBuilderTests.cs b/TestStack.Dossier.Tests/ProxyBuilderTests.cs index e62b2a5..c6c662b 100644 --- a/TestStack.Dossier.Tests/ProxyBuilderTests.cs +++ b/TestStack.Dossier.Tests/ProxyBuilderTests.cs @@ -12,7 +12,7 @@ class ProxyBuilderTests [Fact] public void GivenClassToProxyWithNoProperties_WhenBuildingProxy_ReturnAClassWithNoReturnsValuesSet() { - var proxyBuilder = new ProxyBuilder(new Dictionary()); + var proxyBuilder = new ProxyBuilder(new Dictionary>()); var proxy = proxyBuilder.Build(); @@ -24,7 +24,7 @@ public void GivenClassToProxyWithNoProperties_WhenBuildingProxy_ReturnAClassWith [Fact] public void GivenClassToProxyWithNoProperties_WhenBuildingProxy_ReturnAnNSubstituteProxyOfThatClass() { - var proxyBuilder = new ProxyBuilder(new Dictionary()); + var proxyBuilder = new ProxyBuilder(new Dictionary>()); var proxy = proxyBuilder.Build(); @@ -32,13 +32,14 @@ public void GivenClassToProxyWithNoProperties_WhenBuildingProxy_ReturnAnNSubstit } [Fact] - public void GivenClassToProxyWithSinglePropertyValue_WhenBuildingProxy_ReturnAClassWithReturnValueSet() + public void GivenClassToProxyWithSinglePropertyValue_WhenBuildingProxy_ReturnAClassWithReturnValueSetFromFunction() { - var proxyBuilder = new ProxyBuilder(new Dictionary {{"FirstName", "FirstName"}}); + int nonce = new Random().Next(0, 100); + var proxyBuilder = new ProxyBuilder(new Dictionary> {{"FirstName", () => "FirstName" + nonce}}); var proxy = proxyBuilder.Build(); - proxy.FirstName.ShouldBe("FirstName"); + proxy.FirstName.ShouldBe("FirstName" + nonce); proxy.LastName.ShouldBe(string.Empty); proxy.YearJoined.ShouldBe(0); } @@ -46,11 +47,11 @@ public void GivenClassToProxyWithSinglePropertyValue_WhenBuildingProxy_ReturnACl [Fact] public void GivenClassToProxyWithMultiplePropertyValues_WhenBuildingProxy_ReturnAClassWithReturnValueSet() { - var proxyBuilder = new ProxyBuilder(new Dictionary + var proxyBuilder = new ProxyBuilder(new Dictionary> { - { "FirstName", "FirstName" }, - { "LastName", "LastName" }, - { "YearJoined", 1 }, + { "FirstName", () => "FirstName" }, + { "LastName", () => "LastName" }, + { "YearJoined", () => 1 }, } ); @@ -64,10 +65,10 @@ public void GivenClassToProxyWithMultiplePropertyValues_WhenBuildingProxy_Return [Fact] public void GivenClassWithSomeVirtualProperties_WhenBuildingProxy_ThenOnlyVirtualMembersAreProxied() { - var proxyBuilder = new ProxyBuilder(new Dictionary() + var proxyBuilder = new ProxyBuilder(new Dictionary>() { - {"Name", "Vandelay Industries"}, - {"EmployeeCount", 100} + {"Name", () => "Vandelay Industries"}, + {"EmployeeCount", () => 100} }); var proxy = proxyBuilder.Build(); diff --git a/TestStack.Dossier/ProxyBuilder.cs b/TestStack.Dossier/ProxyBuilder.cs index 5a7ff05..0efd431 100644 --- a/TestStack.Dossier/ProxyBuilder.cs +++ b/TestStack.Dossier/ProxyBuilder.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using NSubstitute; @@ -11,13 +12,13 @@ namespace TestStack.Dossier /// The type being proxied public class ProxyBuilder where T : class { - private readonly Dictionary _properties; + private readonly Dictionary> _properties; /// /// Create a proxy builder to proxy the given property values for the type {T}. /// /// - public ProxyBuilder(Dictionary properties) + public ProxyBuilder(Dictionary> properties) { _properties = properties; } @@ -33,7 +34,7 @@ public T Build() foreach (var property in properties.Where(property => _properties.ContainsKey(property.Name))) { if (property.GetGetMethod().IsVirtual) - property.GetValue(proxy, null).Returns(_properties[property.Name]); + property.GetValue(proxy, null).Returns(_properties[property.Name]()); } return proxy; diff --git a/TestStack.Dossier/TestDataBuilder.cs b/TestStack.Dossier/TestDataBuilder.cs index c3fb0d3..a571dd3 100644 --- a/TestStack.Dossier/TestDataBuilder.cs +++ b/TestStack.Dossier/TestDataBuilder.cs @@ -15,8 +15,7 @@ public abstract class TestDataBuilder where TObject : class where TBuilder : TestDataBuilder, new() { - private readonly Dictionary _properties = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - private readonly Dictionary> _propFactories = new Dictionary>(); + private readonly Dictionary> _properties = new Dictionary>(); private ProxyBuilder _proxyBuilder; /// @@ -118,7 +117,7 @@ protected virtual void AlterProxy(TObject proxy) {} /// The builder so that other method calls can be chained public virtual TBuilder Set(Expression> property, TValue value) { - _properties[Reflector.GetPropertyNameFor(property)] = value; + _properties[Reflector.GetPropertyNameFor(property)] = () => value; return this as TBuilder; } @@ -130,7 +129,7 @@ public virtual TBuilder Set(Expression> property, /// A method which produces instances of {TValue} for the property. public virtual TBuilder Set(Expression> property, Func factory) { - _propFactories[Reflector.GetPropertyNameFor(property)] = () => factory() as object; + _properties[Reflector.GetPropertyNameFor(property)] = () => factory() as object; return this as TBuilder; } @@ -155,11 +154,8 @@ public TValue Get(Expression> property) /// public object Get(Type type, string propertyName) { - object value; - if (_properties.TryGetValue(propertyName, out value)) return value; - Func factory; - if (_propFactories.TryGetValue(propertyName, out factory)) return factory(); + if (_properties.TryGetValue(propertyName, out factory)) return factory(); return Any.Get(type, propertyName); } @@ -208,7 +204,7 @@ protected bool Has(Expression> property) /// Whether or not there is a recorded value for the property protected bool Has(string propertyName) { - return _properties.ContainsKey(propertyName) || _propFactories.ContainsKey(propertyName); + return _properties.ContainsKey(propertyName); } /// From 0d13e75a0d6cacdabe7fe06ae52b23aa9d57dfbe Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Wed, 20 May 2015 19:52:04 -0400 Subject: [PATCH 3/5] Fixes a doc comment --- TestStack.Dossier/TestDataBuilder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/TestStack.Dossier/TestDataBuilder.cs b/TestStack.Dossier/TestDataBuilder.cs index a571dd3..97b9697 100644 --- a/TestStack.Dossier/TestDataBuilder.cs +++ b/TestStack.Dossier/TestDataBuilder.cs @@ -127,6 +127,7 @@ public virtual TBuilder Set(Expression> property, /// The type of the property /// A lambda expression specifying the property to record a value for /// A method which produces instances of {TValue} for the property. + /// The builder so that other method calls can be chained public virtual TBuilder Set(Expression> property, Func factory) { _properties[Reflector.GetPropertyNameFor(property)] = () => factory() as object; From 3b4bc6303c2aad116af9b7ab1a6115e60ad2a461 Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Thu, 21 May 2015 00:47:17 -0400 Subject: [PATCH 4/5] Added the start of a contributor guidelines file - This will help new contributors get started, and keep them from stepping on anyone's stylistic toes. - Expand as you see fit! --- CONTRIBUTOR_GUIDELINES.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CONTRIBUTOR_GUIDELINES.md diff --git a/CONTRIBUTOR_GUIDELINES.md b/CONTRIBUTOR_GUIDELINES.md new file mode 100644 index 0000000..214b5b0 --- /dev/null +++ b/CONTRIBUTOR_GUIDELINES.md @@ -0,0 +1,11 @@ +Contributor Guidelines +====================== + +Hi! Thank you for your interest in contributing to this open source library! +We ask that you follow the following style guidelines when submitting pull +requests, to keep the code consistent and maintainable. + + - Do not put an if clause and it's statement on the same line: separate them + with a new-line and indent accordingly. + +(This file will be expanded as more guidelines are established by the maintainer) \ No newline at end of file From 1d1d47d577c5412f8ddc66291a8cb0cc8fbd9c81 Mon Sep 17 00:00:00 2001 From: Pi Lanningham Date: Thu, 21 May 2015 00:47:38 -0400 Subject: [PATCH 5/5] Fixes a stylistic issue with ifs and statements on the same line --- TestStack.Dossier/TestDataBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TestStack.Dossier/TestDataBuilder.cs b/TestStack.Dossier/TestDataBuilder.cs index 97b9697..24edb91 100644 --- a/TestStack.Dossier/TestDataBuilder.cs +++ b/TestStack.Dossier/TestDataBuilder.cs @@ -156,7 +156,8 @@ public TValue Get(Expression> property) public object Get(Type type, string propertyName) { Func factory; - if (_properties.TryGetValue(propertyName, out factory)) return factory(); + if (_properties.TryGetValue(propertyName, out factory)) + return factory(); return Any.Get(type, propertyName); }