developwithpassion.specifications
developwithpassion.specifications copied to clipboard
Exception "Property set method not found" when observing class with null property
When I run this code on version 0.4.21.5:
public class Test
{
public string Property
{
get { return null; }
}
}
public class when_observing_test : Observes<Test>
{
It should_not_fail_but_it_does = () => Console.WriteLine("Will fail");
}
I get:
System.ArgumentException: Property set method not found.
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index)
at developwithpassion.specifications.core.reflection.PropertyInfoMemberAccessor.change_value_to(Object target, Object new_value) in PropertyInfoMemberAccessor.cs: line 27
at developwithpassion.specifications.core.factories.NonCtorDependencySetter.<>c__DisplayClass7.<attempt_to_update_all_of_the_accessors>b__5(MemberAccessor accessor) in NonCtorDependencySetter.cs: line 42
at developwithpassion.specifications.extensions.IterationExtensions.each(IEnumerable1 items, Action
1 action) in IterationExtensions.cs: line 13
at developwithpassion.specifications.core.factories.NonCtorDependencySetter.attempt_to_update_all_of_the_accessors(IEnumerable1 accessors_to_update, Object target) in NonCtorDependencySetter.cs: line 40 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.update(Object item) in NonCtorDependencySetter.cs: line 35 at developwithpassion.specifications.faking.DefaultSUTFactory
1.create_automatically() in DefaultSUTFactory.cs: line 32
at developwithpassion.specifications.faking.DefaultSUTFactory1.create() in DefaultSUTFactory.cs: line 23 at developwithpassion.specifications.DefaultTestStateFor
1.build_sut() in DefaultTestStateFor.cs: line 32
at developwithpassion.specifications.DefaultTestStateFor1.run_setup() in DefaultTestStateFor.cs: line 43 at developwithpassion.specifications.DefaultObservationController
2.run_setup() in DefaultObservationController.cs: line 51
at developwithpassion.specifications.observations.InstanceObservations`3.<.ctor>b__0() in InstanceObservations.cs: line 14
In my production code Test.Property isn't really implemented like that. It's a calculated property and it just returns null in certain situations. Our unit tests fail then.
When Test class looks like this: public class Test { public string Property { get { return "property value"; } } }
all works fine.
It was working ok with version 0.4.13.0 which we were using previously.
I will patch this today.
The issue is to do with the fact that dwp.specs now supports doing property injection as well as ctor injection (or a mix).
I must have omitted to check that the property can be written to (I'll write a failing spec and get it passing!!)
God Bless You,
JP
On Tue, Aug 16, 2011 at 9:38 AM, marcinbelczewski < [email protected]>wrote:
When I run this code on version 0.4.21.5:
public class Test { public string Property { get { return null; } } }
public class when_observing_test : Observes<Test> { It should_not_fail_but_it_does = () => Console.WriteLine("Will fail"); }
I get:
System.ArgumentException: Property set method not found. at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) at developwithpassion.specifications.core.reflection.PropertyInfoMemberAccessor.change_value_to(Object target, Object new_value) in PropertyInfoMemberAccessor.cs: line 27 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.<>c__DisplayClass7.<attempt_to_update_all_of_the_accessors>b__5(MemberAccessor accessor) in NonCtorDependencySetter.cs: line 42 at developwithpassion.specifications.extensions.IterationExtensions.each(IEnumerable
1 items, Action
1 action) in IterationExtensions.cs: line 13 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.attempt_to_update_all_of_the_accessors(IEnumerable1 accessors_to_update, Object target) in NonCtorDependencySetter.cs: line 40 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.update(Object item) in NonCtorDependencySetter.cs: line 35 at developwithpassion.specifications.faking.DefaultSUTFactory
1.create_automatically() in DefaultSUTFactory.cs: line 32 at developwithpassion.specifications.faking.DefaultSUTFactory1.create() in DefaultSUTFactory.cs: line 23 at developwithpassion.specifications.DefaultTestStateFor
1.build_sut() in DefaultTestStateFor.cs: line 32 at developwithpassion.specifications.DefaultTestStateFor1.run_setup() in DefaultTestStateFor.cs: line 43 at developwithpassion.specifications.DefaultObservationController
2.run_setup() in DefaultObservationController.cs: line 51 at developwithpassion.specifications.observations.InstanceObservations`3.<.ctor>b__0() in InstanceObservations.cs: line 14In my production code Test.Property isn't really implemented like that. It's a calculated property and it just returns null in certain situations. Our unit tests fail then.
When Test class looks like this: public class Test { public string Property { get { return "property value"; } } }
all works fine.
It was working ok with version 0.4.13.0 which we were using previously.
Reply to this email directly or view it on GitHub:
https://github.com/developwithpassion/developwithpassion.specifications/issues/3
God Bless You,
JP
Develop With Passion web site: http://www.developwithpassion.com blog: http://blog.developwithpassion.com http://blog.jpboodhoo.com/ email: [email protected] [email protected] phone: (503)213-3507
If you update to the latest nuget package, the fix should be there.
The feature that was added enables all the different types of dependency injection for the sut (ctor, field, and property).
If you have any questions, please do not hesitate to get in contact with me.
God Bless You,
JP
On Tue, Aug 16, 2011 at 9:38 AM, marcinbelczewski < [email protected]>wrote:
When I run this code on version 0.4.21.5:
public class Test { public string Property { get { return null; } } }
public class when_observing_test : Observes<Test> { It should_not_fail_but_it_does = () => Console.WriteLine("Will fail"); }
I get:
System.ArgumentException: Property set method not found. at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture) at System.Reflection.RuntimePropertyInfo.SetValue(Object obj, Object value, Object[] index) at developwithpassion.specifications.core.reflection.PropertyInfoMemberAccessor.change_value_to(Object target, Object new_value) in PropertyInfoMemberAccessor.cs: line 27 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.<>c__DisplayClass7.<attempt_to_update_all_of_the_accessors>b__5(MemberAccessor accessor) in NonCtorDependencySetter.cs: line 42 at developwithpassion.specifications.extensions.IterationExtensions.each(IEnumerable
1 items, Action
1 action) in IterationExtensions.cs: line 13 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.attempt_to_update_all_of_the_accessors(IEnumerable1 accessors_to_update, Object target) in NonCtorDependencySetter.cs: line 40 at developwithpassion.specifications.core.factories.NonCtorDependencySetter.update(Object item) in NonCtorDependencySetter.cs: line 35 at developwithpassion.specifications.faking.DefaultSUTFactory
1.create_automatically() in DefaultSUTFactory.cs: line 32 at developwithpassion.specifications.faking.DefaultSUTFactory1.create() in DefaultSUTFactory.cs: line 23 at developwithpassion.specifications.DefaultTestStateFor
1.build_sut() in DefaultTestStateFor.cs: line 32 at developwithpassion.specifications.DefaultTestStateFor1.run_setup() in DefaultTestStateFor.cs: line 43 at developwithpassion.specifications.DefaultObservationController
2.run_setup() in DefaultObservationController.cs: line 51 at developwithpassion.specifications.observations.InstanceObservations`3.<.ctor>b__0() in InstanceObservations.cs: line 14In my production code Test.Property isn't really implemented like that. It's a calculated property and it just returns null in certain situations. Our unit tests fail then.
When Test class looks like this: public class Test { public string Property { get { return "property value"; } } }
all works fine.
It was working ok with version 0.4.13.0 which we were using previously.
Reply to this email directly or view it on GitHub:
https://github.com/developwithpassion/developwithpassion.specifications/issues/3
God Bless You,
JP
Develop With Passion web site: http://www.developwithpassion.com blog: http://blog.developwithpassion.com http://blog.jpboodhoo.com/ email: [email protected] [email protected] phone: (503)213-3507
Sorry for late response on that JP. I've played around with the fix and discovered one more interesting issue. Or maybe this is not an issue - that depends on the context. Let's imagine a weird scenario where property injection interferes with what happens in a constructor:
public class TestData
{
public string Data { get; set; }
}
public class TestPropertyInjection
{
public TestData TestData { get; private set; }
public TestPropertyInjection(TestData testData)
{
TestData = new TestData {Data = string.Concat(testData.Data, ":injected")};
}
}
public class when_creating_sut_with_property_injected_through_constructor_that_alters_it_immediately : Observes<TestPropertyInjection>
{
Establish context = () =>
depends.on(new TestData {Data = "data"});
It should_not_perform_property_injection_property_has_private_setter_anyway_= () =>
sut.TestData.Data.ShouldEqual("data:injected");
}
This spec fails and some developers, even aware of property injection might be surprised as they tend to think that private setter would be enough to discourage property injection.
I've noticed that CanWrite only checks if there is a setter but doesn't really say anything about it being public or not. Not sure if changing "PropertyInfoMemberAccessor.change_value_to" method to:
public void change_value_to(object target,object new_value)
{
if (member.CanWrite && member.GetSetMethod()!=null) this.member.SetValue(target, new_value, null);
}
would be good enough of a fix.
Cheers, Marcin
OK I've just noticed that this is by design. There is a test in "and_the_property_has_a_private_mutator" that verifies this. So I have another question. How to disable property injection altogether? We have 2000+ unit tests and some break once property injection is enabled. Dealing with property injection through private setter would be a problem for us now so in order to migrate to newer version (for R# 6.0 reasons) I'd like to disable property injection.
Hey Marcin,
If there is no value for the property type in the dependency bag and the property has a default value it will not get injected.
Can you show me an example of one of your specs that is breaking?
God Bless You,
JP
On Tue, Aug 30, 2011 at 7:57 AM, marcinbelczewski < [email protected]>wrote:
OK I've just noticed that this is by design. There is a test in "and_the_property_has_a_private_mutator" that verifies this. So I have another question. How to disable property injection altogether? We have 2000+ unit tests and some break once property injection is enabled. Dealing with property injection through private setter would be a problem for us now so in order to migrate to newer version (for R# 6.0 reasons) I'd like to disable property injection.
Reply to this email directly or view it on GitHub:
https://github.com/developwithpassion/developwithpassion.specifications/issues/3#issuecomment-1942223
God Bless You,
JP
Develop With Passion web site: http://www.developwithpassion.com blog: http://blog.developwithpassion.com http://blog.jpboodhoo.com/ email: [email protected] [email protected] phone: (503)213-3507
JP
Check out this example:
public class TestData { public string Data { get; set; } }
public class TestPropertyInjection
{
public TestData TestData { get; private set; }
public TestPropertyInjection(TestData testData)
{
TestData = new TestData { Data = string.Concat(testData.Data, ":injected into constructor") };
}
}
public class when_creating_sut_with_object_injected_through_constructor : Observes<TestPropertyInjection>
{
Establish context = () =>
depends.on(new TestData { Data = "data" });
It should_equal_to_value_set_in_the_constructor = () =>
sut.TestData.Data.ShouldEqual("data:injected into constructor");
}
I know that this might be an awkward example. We get object injected into constructor but we set the actual property to another value that is just based on or derived from the one injected. In this case dependency bag contains TestData {Data="data"} that is injected again into property overwriting whatever we did in the constructor.
If we structured code with private field rather than private setter all works fine:
public class TestData
{
public string Data { get; set; }
}
public class TestPropertyInjection
{
readonly TestData _testData;
public TestData TestData
{
get { return _testData; }
}
public TestPropertyInjection(TestData testData)
{
_testData = new TestData { Data = string.Concat(testData.Data, ":injected into constructor") };
}
}
public class when_creating_sut_with_object_injected_through_constructor : Observes<TestPropertyInjection>
{
Establish context = () =>
depends.on(new TestData { Data = "data" });
It should_equal_to_value_set_in_the_constructor = () =>
sut.TestData.Data.ShouldEqual("data:injected into constructor");
}
Thus my concern. The difference between private field and private setter for our developers was pretty narrow and they got confused that something got injected into their private setters.
Does that make sense? Cheers, Marcin