Unit testing that events are raised in C# (in order)
Asked Answered
O

7

167

I have some code that raises PropertyChanged events and I would like to be able to unit test that the events are being raised correctly.

The code that is raising the events is like

public class MyClass : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;  

   protected void NotifyPropertyChanged(String info)
   {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
   }  

   public string MyProperty
   {
       set
       {
           if (_myProperty != value)
           {
               _myProperty = value;
               NotifyPropertyChanged("MyProperty");
           }
       }
   }
}

I get a nice green test from the following code in my unit tests, that uses delegates:

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    string actual = null;
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
         actual = e.PropertyName;
    };

    myClass.MyProperty = "testing";
    Assert.IsNotNull(actual);
    Assert.AreEqual("MyProperty", actual);
}

However, if I then try and chain the setting of properties together like so:

public string MyProperty
{
    set
    {
        if (_myProperty != value)
        {
            _myProperty = value;
            NotifyPropertyChanged("MyProperty");
            MyOtherProperty = "SomeValue";
        }
    }
}

public string MyOtherProperty
{
    set
    {
        if (_myOtherProperty != value)
        {
            _myOtherProperty = value;
            NotifyPropertyChanged("MyOtherProperty");
        }
    }
}

My test for the event fails - the event that it captures is the event for the MyOtherProperty.

I'm pretty sure the event fires, my UI reacts like it does, but my delegate only captures the last event to fire.

So I'm wondering:
1. Is my method of testing events correct?
2. Is my method of raising chained events correct?

Oreopithecus answered 30/10, 2008 at 1:21 Comment(0)
F
204

Everything you've done is correct, providing you want your test to ask "What is the last event that was raised?"

Your code is firing these two events, in this order

  • Property Changed (... "My Property" ...)
  • Property Changed (... "MyOtherProperty" ...)

Whether this is "correct" or not depends upon the purpose of these events.

If you want to test the number of events that gets raised, and the order they get raised in, you can easily extend your existing test:

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    List<string> receivedEvents = new List<string>();
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
        receivedEvents.Add(e.PropertyName);
    };

    myClass.MyProperty = "testing";
    Assert.AreEqual(2, receivedEvents.Count);
    Assert.AreEqual("MyProperty", receivedEvents[0]);
    Assert.AreEqual("MyOtherProperty", receivedEvents[1]);
}
Faubert answered 30/10, 2008 at 1:45 Comment(1)
Shorter version: myClass.PropertyChanged += (object sender, e) => receivedEvents.Add(e.PropertyName);Relly
B
22

If you're doing TDD then event testing can start to generate a lot of repetitive code. I wrote an event monitor that enables a much cleaner approach to unit test writing for these situations.

var publisher = new PropertyChangedEventPublisher();

Action test = () =>
{
    publisher.X = 1;
    publisher.Y = 2;
};

var expectedSequence = new[] { "X", "Y" };

EventMonitor.Assert(test, publisher, expectedSequence);

Please see my answer to the following for more details.

Unit testing that an event is raised in C#, using reflection

Barrel answered 23/4, 2010 at 13:4 Comment(1)
Second link is down.Mattoid
Y
11

This is very old and probably wont even be read but with some cool new .net features I have created an INPC Tracer class that allows that:

[Test]
public void Test_Notify_Property_Changed_Fired()
{
    var p = new Project();

    var tracer = new INCPTracer();

    // One event
    tracer.With(p).CheckThat(() => p.Active = true).RaisedEvent(() => p.Active);

    // Two events in exact order
    tracer.With(p).CheckThat(() => p.Path = "test").RaisedEvent(() => p.Path).RaisedEvent(() => p.Active);
}

See gist: https://gist.github.com/Seikilos/6224204

Yasui answered 13/8, 2013 at 18:41 Comment(2)
Beautiful - you should consider packaging it up and publishing it on nuget.orgCiracirca
Great work! I really dig the fluent API. I have done something similar myself (github.com/f-tischler/EventTesting) but I think your approach is even more concise.Amplifier
B
6

Below is a slightly changed Andrew's code which instead of just logging the sequence of raised events rather counts how many times a specific event has been called. Although it is based on his code I find it more useful in my tests.

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    Dictionary<string, int> receivedEvents = new Dictionary<string, int>();
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
        if (receivedEvents.ContainsKey(e.PropertyName))
            receivedEvents[e.PropertyName]++;
        else
            receivedEvents.Add(e.PropertyName, 1);
    };

    myClass.MyProperty = "testing";
    Assert.IsTrue(receivedEvents.ContainsKey("MyProperty"));
    Assert.AreEqual(1, receivedEvents["MyProperty"]);
    Assert.IsTrue(receivedEvents.ContainsKey("MyOtherProperty"));
    Assert.AreEqual(1, receivedEvents["MyOtherProperty"]);
}
Bebeeru answered 6/12, 2010 at 21:4 Comment(0)
K
1

Based on this article, i have created this simple assertion helper :

private void AssertPropertyChanged<T>(T instance, Action<T> actionPropertySetter, string expectedPropertyName) where T : INotifyPropertyChanged
    {
        string actual = null;
        instance.PropertyChanged += delegate (object sender, PropertyChangedEventArgs e)
        {
            actual = e.PropertyName;
        };
        actionPropertySetter.Invoke(instance);
        Assert.IsNotNull(actual);
        Assert.AreEqual(propertyName, actual);
    }

With this method helper, the test becomes really simple.

[TestMethod()]
public void Event_UserName_PropertyChangedWillBeFired()
{
    var user = new User();
    AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
}
Kowalczyk answered 28/11, 2015 at 14:7 Comment(0)
T
1

Don't write a test for each member - this is much work

(maybe this solution is not perfect for every situation - but it shows a possible way. You might need to adapt it for your use case)

It's possible to use reflection in a library to test if your members are all responding to your property changed event correctly:

  • PropertyChanged event is raised on setter access
  • Event is raised correct (name of property equals argument of raised event)

The following code can be used as a library and shows how to test the following generic class

using System.ComponentModel;
using System.Linq;

/// <summary>
/// Check if every property respons to INotifyPropertyChanged with the correct property name
/// </summary>
public static class NotificationTester
    {
        public static object GetPropertyValue(object src, string propName)
        {
            return src.GetType().GetProperty(propName).GetValue(src, null);
        }

        public static bool Verify<T>(T inputClass) where T : INotifyPropertyChanged
        {
            var properties = inputClass.GetType().GetProperties().Where(x => x.CanWrite);
            var index = 0;

            var matchedName = 0;
            inputClass.PropertyChanged += (o, e) =>
            {
                if (properties.ElementAt(index).Name == e.PropertyName)
                {
                    matchedName++;
                }

                index++;
            };

            foreach (var item in properties)
            { 
                // use setter of property
                item.SetValue(inputClass, GetPropertyValue(inputClass, item.Name));
            }

            return matchedName == properties.Count();
        }
    }

The tests of your class can now be written as. (maybe you want to split the test into "event is there" and "event raised with correct name" - you can do this yourself)

[TestMethod]
public void EveryWriteablePropertyImplementsINotifyPropertyChangedCorrect()
{
    var viewModel = new TestMyClassWithINotifyPropertyChangedInterface();
    Assert.AreEqual(true, NotificationTester.Verify(viewModel));
}

Class

using System.ComponentModel;

public class TestMyClassWithINotifyPropertyChangedInterface : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        private int id;

        public int Id
        {
            get { return id; }
            set { id = value;
                NotifyPropertyChanged("Id");
            }
        }
}
Term answered 14/1, 2016 at 10:8 Comment(1)
I tried this, but if my property setters have a guard statement like "if(value == _myValue) return", which all mine do, then the above won't work, unless I'm missing something. I've recently come from C++ to C#.Misinterpret
S
0

I've made an extension here:

public static class NotifyPropertyChangedExtensions
{
    private static bool _isFired = false;
    private static string _propertyName;

    public static void NotifyPropertyChangedVerificationSettingUp(this INotifyPropertyChanged notifyPropertyChanged,
      string propertyName)
    {
        _isFired = false;
        _propertyName = propertyName;
        notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
    }

    private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _propertyName)
        {
            _isFired = true;
        }
    }

    public static bool IsNotifyPropertyChangedFired(this INotifyPropertyChanged notifyPropertyChanged)
    {
        _propertyName = null;
        notifyPropertyChanged.PropertyChanged -= OnPropertyChanged;
        return _isFired;
    }
}

There is the usage:

   [Fact]
    public void FilesRenameViewModel_Rename_Apply_Execute_Verify_NotifyPropertyChanged_If_Succeeded_Through_Extension_Test()
    {
        //  Arrange
        _filesViewModel.FolderPath = ConstFolderFakeName;
        _filesViewModel.OldNameToReplace = "Testing";
        //After the command's execution OnPropertyChanged for _filesViewModel.AllFilesFiltered should be raised
        _filesViewModel.NotifyPropertyChangedVerificationSettingUp(nameof(_filesViewModel.AllFilesFiltered));
        //Act
        _filesViewModel.ApplyRenamingCommand.Execute(null);
        // Assert
        Assert.True(_filesViewModel.IsNotifyPropertyChangedFired());

    }
Shul answered 17/2, 2017 at 13:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.