bitblack / moq

Automatically exported from code.google.com/p/moq
Other
0 stars 0 forks source link

NullReferenceException when subscribing to an event #361

Open GoogleCodeExporter opened 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
    [Fact]
    public void Reproduce()
    {
      var mock = new Mock<ObservableCollection<int>>();
      INotifyPropertyChanged observable = mock.Object;
      observable.PropertyChanged += (sender, args) => { };  
    }

What is the expected output? What do you see instead?
  Expected:   
    No Exception

  Output:
    Test(s) failed. System.NullReferenceException : Object reference not set to an instance of an object.
      at Moq.AddActualInvocation.AddEventHandler(EventInfo ev, Delegate handler) in InterceptorStrategies.cs: line 242
      at Moq.AddActualInvocation.HandleIntercept(ICallContext invocation, InterceptStrategyContext ctx) in InterceptorStrategies.cs: line 277
      at Moq.Interceptor.Intercept(ICallContext invocation) in Interceptor.cs: line 160
      at Moq.Proxy.CastleProxyFactory.Interceptor.Intercept(IInvocation invocation) in CastleProxyFactory.cs: line 111
      at Castle.DynamicProxy.AbstractInvocation.Proceed()
      at Moq.Tests.MockExplicitEventForwardedToProtectedEventFixture.Reproduce()

What version of the product are you using? On what operating system?
  Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920
  Windows 7 Service Pack 1 , .Net Framework 4

Please provide any additional information below.

I'm using Moq to create mocks of my WPF view models to test my WPF view 
bindings. Therefore I create a mock of the view model with 
'repository.DefaultValue = DefaultValue.Mock;' this works perfect as long as 
the view models do not provide a ObservableCollection. When they use 
ObservableCollection the above mentioned exception occurs.

The problem is that ObservableCollection explicit implements the 
INotifyPropertyChanged and delegates the implementation to an protected virtual 
event of the same name. Because this event is protected Moq 
(AddActualInvocation.GetEventFromName()) does not find the event info and just 
return null which afterwards results in the NullReferenceException. The 
following code shows the problem without using the ObservableCollection:

public interface IInterfaceWithEvent
    {
        event EventHandler<EventArgs> JustAnEvent;
    }

    public class Implementation : IInterfaceWithEvent
    {
        private EventHandler<EventArgs> _justAnEvent;

        event EventHandler<EventArgs> IInterfaceWithEvent.JustAnEvent
        {
            add { this.JustAnEvent += value; }
            remove { this.JustAnEvent -= value; }
        }

        protected virtual event EventHandler<EventArgs> JustAnEvent
        {
            add
            {
                EventHandler<EventArgs> changedEventHandler = this._justAnEvent;
                EventHandler<EventArgs> comparand;
                do
                {
                    comparand = changedEventHandler;
                    changedEventHandler = Interlocked.CompareExchange<EventHandler<EventArgs>>(ref this._justAnEvent, comparand + value, comparand);
                }
                while (changedEventHandler != comparand);
            }
            remove
            {
                EventHandler<EventArgs> changedEventHandler = this._justAnEvent;
                EventHandler<EventArgs> comparand;
                do
                {
                    comparand = changedEventHandler;
                    changedEventHandler = Interlocked.CompareExchange<EventHandler<EventArgs>>(ref this._justAnEvent, comparand - value, comparand);
                }
                while (changedEventHandler != comparand);
            }
        }
    }

    [Fact]
    public void ReproduceBug()
    {
        var mock = new Mock<Implementation>();
        IInterfaceWithEvent observable = mock.Object;

        observable.JustAnEvent += (sender, args) => { };

    }

The following patch would solve the problem and not break any existing tests. 
It searches also for non public events if it does not find a public one:

diff --git a/Source/InterceptorStrategies.cs b/Source/InterceptorStrategies.cs
index 247216e..de95cfa 100644
--- a/Source/InterceptorStrategies.cs
+++ b/Source/InterceptorStrategies.cs
@@ -190,9 +190,36 @@ namespace Moq
                                        depthFirstProgress.Enqueue(implementedType);
                                }
                        }
-
-                       return null;
+            return GetNonPublicEventFromName(eventName);
                }
+
+        /// <summary>
+        /// Get an eventInfo for a given event name.  Search type ancestors 
depth first if necessary.
+        /// </summary>
+        /// <param name="eventName">Name of the event, with the set_ or get_ 
prefix already removed</param>
+        private EventInfo GetNonPublicEventFromName(string eventName)
+        {
+            var depthFirstProgress = new 
Queue<Type>(ctx.Mock.ImplementedInterfaces.Skip(1));
+            depthFirstProgress.Enqueue(ctx.TargetType);
+            while (depthFirstProgress.Count > 0)
+            {
+                var currentType = depthFirstProgress.Dequeue();
+                var eventInfo = currentType.GetEvent(eventName, 
BindingFlags.Instance | BindingFlags.NonPublic);
+                if (eventInfo != null)
+                {
+                    return eventInfo;
+                }
+
+                foreach (var implementedType in GetAncestorTypes(currentType))
+                {
+                    depthFirstProgress.Enqueue(implementedType);
+                }
+            }
+
+            return null;
+        }
+
+
                /// <summary>
                /// Given a type return all of its ancestors, both types and interfaces.
                /// </summary>

This fixes my problem so I can test my views for binding errors, but I don't 
now if it produces undesired side effects.

Regards 
Franz

Original issue reported on code.google.com by schnyderfranz@gmail.com on 19 Mar 2013 at 4:15

GoogleCodeExporter commented 9 years ago
I forgot to mention that the fix is against the head of the Moq4 dev branch 
from github.

I now also created a pull request for the path: 
https://github.com/Moq/moq4/pull/39

Original comment by schnyderfranz@gmail.com on 19 Mar 2013 at 11:13