[TestFixture]public class When_model_property_changes{ [SetUp] public void SetUp() { EstablishContext(); Because(); } protected override void EstablishContext() { } protected override void Because() { } [Test] public void A_notify_property_changed_event_should_be_raised() { }}Next, we’ll establish the context of our test by configuring an instance of the Unity container to intercept methods for a type named IModel:
[TestFixture]public class When_model_property_changes : ContextSpecification{ IUnityContainer container; IModel model; /** snip **/ protected void EstablishContext() { container = new UnityContainer(); container.AddNewExtension() .RegisterType<IModel, Model>() .Configure() .SetDefaultInterceptorFor<IModel>(new TransparentProxyInterceptor()); model = container.Resolve<IModel>(); } /** snip **/}We configure our container by first adding the Interception extension. Next, we register the type we will be resolving from the container. Finally, we set a default interceptor for our type.By adding the Interception extension, two new build strategies are added to the chain-of-responsibility for facilitating interception. Additionally, the extension registers an instance of an AttributeDrivenPolicy which is used by the interception strategies to provide the ability to identify which methods should be intercepted through the use of attributes. We will later create a custom attribute derived from HandlerAttribute which will match our type with the AttributeDrivenPolicy.Note: The AttributeDrivenPolicy is a policy type internal to the Interception extension and is not related to the IBuilderPolicy types used by the Object Builder component.Our last step in establishing the context for our test is to have the container create an instance of our registered type.Next, we’ll define the behavior of our test to be observed:
protected override void Because(){ model.PropertyChanged += (sender, args) => { propertyChangedEventRaised = true; }; model.TestProperty = "[any value]";}Here, we establish that the model should have a public event named PropertyChanged and a public writable string member named TestProperty. When the event is fired, we’ll set a Boolean named propertyChangedEventRaised to true which we’ll test for later in our observation.Our next step is to go ahead and create the types we’ve registered with the container and declare our propertyChangedEventRaised Boolean member:
[TestFixture]public class When_model_property_changes{ IUnityContainer container; IModel model; bool propertyChangedEventRaised; internal interface IModel : INotifyPropertyChanged { string TestProperty { get; set; } } internal class Model : IModel { public string TestProperty { get; set; } public event PropertyChangedEventHandler PropertyChanged; } /** snip **/}Notice that we currently have no code within the Model type to actually raise the PropertyChanged event.Next, we need to provide the observation portion of our test:
bool propertyChangedEventRaised;[Test]public void Should_raise_notify_property_changed_event(){ Assert.That(propertyChangedEventRaised, Is.True);}Running the test at this point produces the expected failure result:To make the test pass, we need to configure our type to match an interception policy and provide an interception handler which will raise thePropertyChanged event on behalf of our type. Since we’re using the default AttributeDrivenPolicy to have the interception strategies recognize our type as one to be intercepted, we need to create an attribute derived from the extension’s HandlerAttribute:
/// <summary>/// This attribute is used to denote that the event is to be intercepted./// </summary>public class NotifyAttribute : HandlerAttribute{ readonly ICallHandler handler; public NotifyAttribute() { handler = new NotifyPropertyChangedCallHandler(); } public override ICallHandler CreateHandler(IUnityContainer container) { return handler; }}The HandlerAttribute is used by the AttributeDrivenPolicy to return an ICallHandler. The ICallHandler type encapsulates the behavior used to intercept method calls for the configured types within Unity. Here, we return a NotifyPropertyChangedCallHandler() which will intercept all call to methods adorned with our custom attribute.Next, we create our NotifyPropertyChangedCallHandler to raise the PropertyChanged event for our property:
/// <summary>/// This class handler is used in conjunction with Unity to handle INotifiyPropertyChanged/// implementations as an aspect./// </summary>class NotifyPropertyChangedCallHandler : ICallHandler{ public int Order { get { return 0; } set { } } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { IMethodReturn result = getNext.Invoke().Invoke(input, getNext); MethodBase method = input.MethodBase; if (method.IsSpecialName && method.Name.StartsWith("set_")) { // Get the property name string propertyName = method.Name.Substring(4); // Get the private event backing field FieldInfo info = input.Target.GetType() .GetFields(BindingFlags.InstanceBindingFlags.NonPublic) .Where(f => f.FieldType == typeof (PropertyChangedEventHandler)) .FirstOrDefault(); if (info != null) { var handler = info.GetValue(input.Target) as PropertyChangedEventHandler; // Ensure subscriptions if (handler != null) { handler.Invoke(input.Target, new PropertyChangedEventArgs(propertyName)); } } } return result; }}Finally, we need to add our custom attribute to the property of our type for which we want our interception to occur:
internal class Model : IModel{ public event PropertyChangedEventHandler PropertyChanged; public string TestProperty { get; [Notify] set; }}Running our test again shows that it now passes!
联系客服