Тестовые события с помощью nunit - PullRequest
35 голосов
/ 02 августа 2010

Я только начинаю с TDD и могу решить большинство проблем, с которыми столкнулся самостоятельно.Но теперь я потерян: как я могу проверить, запускаются ли события?Я искал что-то вроде Assert.Raise или Assert.Fire, но там ничего нет.Google был не очень полезен, большинство хитов были предложения типа foo.myEvent += new EventHandler(bar); Assert.NotNull(foo.myEvent);, но это ничего не доказывает.

Спасибо!

Ответы [ 9 ]

46 голосов
/ 02 августа 2010

Проверка наличия событий может быть выполнена путем подписки на это событие и установки логического значения:

var wasCalled = false;
foo.NyEvent += (o,e) => wasCalled = true;

...

Assert.IsTrue(wasCalled);

По запросу - без лямбд:

var wasCalled = false;
foo.NyEvent += delegate(o,e){ wasCalled = true;}

...

Assert.IsTrue(wasCalled);
13 голосов
/ 10 сентября 2015

Я предпочитаю делать следующее:

var wait = new AutoResetEvent(false);
foo.MeEvent += (sender, eventArgs) => { wait.Set(); };
Assert.IsTrue(wait.WaitOne(TimeSpan.FromSeconds(5)));

Преимущества: Поддерживает сценарий многопоточности (если обработчик вызывается в другом потоке)

10 голосов
/ 05 декабря 2013

Если вы знаете, что событие будет запущено синхронно:

bool eventRaised = false;
Customer customer = new Customer() { Name = "Carl" };
customer.NameChanged += (sender, e) => { eventRaised = true; };

customer.Name = "Sam";

Assert.IsTrue(eventRaised);

Если событие может быть запущено асинхронно:

ManualResetEvent eventRaised = new ManualResetEvent(false);
Customer customer = new Customer() { Name = "Carl" };
customer.NameChanged += (sender, e) => { eventRaised.Set(); };

customer.Name = "Sam";

Assert.IsTrue(eventRaised.WaitOne(TIMEOUT));

Однако некоторые говорят, что следует избегать тестирования асинхронного поведения.

5 голосов
/ 15 июня 2012

Я недавно должен был сделать это, и ниже то, что я придумал.Причина, по которой я не сделал того, о чем говорили другие посты, заключается в том, что мне не нравится идея сохранения состояния переменной и необходимости сбрасывать его «вручную» между несколькими событиями.* с NameChanged событием, которое тестируется в MyTests тестах:

public class ClassUnderTest {
    private string name;
    public string Name {
        get { return this.name; }
        set {
            if (value != this.name) {
                this.name = value;
                NameChanged(this, new PropertyChangedEventArgs("Name"));
            }
        }
    }

    public event EventHandler<PropertyChangedEventArgs> NameChanged = delegate { };
}

[TestFixture]
public class MyTests {
    [Test]
    public void Test_SameValue() {
        var t = new ClassUnderTest();
        var e = new EventHandlerCapture<PropertyChangedEventArgs>();
        t.NameChanged += e.Handler;

        Event.Assert(e, Event.IsNotRaised<PropertyChangedEventArgs>(), () => t.Name = null);
        t.Name = "test";
        Event.Assert(e, Event.IsNotRaised<PropertyChangedEventArgs>(), () => t.Name = "test");
    }
    [Test]
    public void Test_DifferentValue() {
        var t = new ClassUnderTest();
        var e = new EventHandlerCapture<PropertyChangedEventArgs>();
        t.NameChanged += e.Handler;

        Event.Assert(e, Event.IsPropertyChanged(t, "Name"), () => t.Name = "test");
        Event.Assert(e, Event.IsPropertyChanged(t, "Name"), () => t.Name = null);
    }
}

Поддерживающие классы приведены ниже.Классы могут использоваться с любым EventHandler<TEventArgs> или расширяться для других делегатов.Тесты событий могут быть вложенными.

/// <summary>Class to capture events</summary>
public class EventHandlerCapture<TEventArgs> where TEventArgs : EventArgs {
    public EventHandlerCapture() {
        this.Reset();
    }

    public object Sender { get; private set; }
    public TEventArgs EventArgs { get; private set; }
    public bool WasRaised { get; private set; }

    public void Reset() {
        this.Sender = null;
        this.EventArgs = null;
        this.WasRaised = false;
    }

    public void Handler(object sender, TEventArgs e) {
        this.WasRaised = true;
        this.Sender = sender;
        this.EventArgs = e;
    }
}

/// <summary>Contains things that make tests simple</summary>
public static class Event {
    public static void Assert<TEventArgs>(EventHandlerCapture<TEventArgs> capture, Action<EventHandlerCapture<TEventArgs>> test, Action code) where TEventArgs : EventArgs {
        capture.Reset();
        code();
        test(capture);
    }
    public static Action<EventHandlerCapture<TEventArgs>> IsNotRaised<TEventArgs>() where TEventArgs : EventArgs {
        return (EventHandlerCapture<TEventArgs> test) => {
            NUnit.Framework.Assert.That(test.WasRaised, Is.False);
        };
    }
    public static Action<EventHandlerCapture<PropertyChangedEventArgs>> IsPropertyChanged(object sender, string name) {
        return (EventHandlerCapture<PropertyChangedEventArgs> test) => {
            NUnit.Framework.Assert.That(test.WasRaised, Is.True);
            NUnit.Framework.Assert.That(test.Sender, Is.SameAs(sender));
            NUnit.Framework.Assert.That(test.EventArgs.PropertyName, Is.EqualTo(name));
        };
    }
}
2 голосов
/ 15 января 2018

Используя NUnit и Moq, вы можете сделать более надежное тестирование событий.

Класс Mock, используемый для мониторинга триггеров событий:

public class AssertEvent { public virtual void Call(string obj) { } }
Mock<AssertEvent> EventMock;
AssertEvent Evt;

Настройка для триггеров событий:

[SetUp]
public void TestInit() {
    EventMock = new Mock<AssertEvent>();
    Evt= EventMock.Object;
}

Использование фиктивных объектов в тестах:

[Test]
public void TestMethod() {
    myObject.Event1 += (sender, args) => Evt.Call("Event1Label");
    myObject.Event2 += (sender, args) => Evt.Call("Event2Label");
    myObject.Event3 += (sender, args) => Evt.Call("Event3Label");        

    myObject.SomeEventTrigger();

    EventMock.Verify(m => m.Call("Event1Label"), Times.Exactly(1));
    EventMock.Verify(m => m.Call("Event2Label"), Times.Never());
    EventMock.Verify(m => m.Call("Event3Label"), Times.Between(1,3);

}
1 голос
/ 02 августа 2010

@ theburningmonk: A ";"пропал, отсутствует.Исправленная версия:

bool eventFired = false;
foo.MyEvent += (s, e) => { eventFired = true; };
Assert.IsTrue(eventFired);

Ура!; -)

1 голос
/ 02 августа 2010

На самом деле не сделал этого сам, но, возможно, вы могли бы добавить фиктивный обработчик события, на которое хотите подписаться, и попросить его обновить локальную логическую переменную, чтобы после запуска метода вы могли проверить состояние этого логического значения, чтобы увидетьесли событие было запущено?

Что-то вроде:

bool eventFired = false;
foo.MyEvent += (s, e) => { eventFired = true };

Assert.IsTrue(eventFired);
0 голосов
/ 11 июля 2019

Я бы просто использовал FluentAssertions вместе с Nunit: https://fluentassertions.com/eventmonitoring/ это работает очень хорошо. Вот пример из документации

var subject = new EditCustomerViewModel();
using (var monitoredSubject = subject.Monitor())
{
    subject.Foo();
    monitoredSubject.Should().Raise("NameChangedEvent");
}
0 голосов
/ 02 августа 2010

Вы можете добавить свой собственный обработчик событий, который, например, увеличивает некоторое целое поле в классе тестового примера.А затем проверьте, увеличилось ли поле.

...