使用 nunit 测试事件

发布于 2024-09-12 19:36:50 字数 264 浏览 5 评论 0原文

我刚刚开始使用 TDD,并且可以自己解决我遇到的大部分问题。但现在我迷失了:如何检查事件是否被触发?我一直在寻找类似 Assert.RaiseAssert.Fire 的东西,但什么也没有。 Google 不是很有用,大多数点击都是诸如 foo.myEvent += new EventHandler(bar); 之类的建议。 Assert.NotNull(foo.myEvent); 但这并不能证明什么。

谢谢你!

I'm just starting with TDD and could solve most of the problems I've faced on my own. But now I'm lost: How can I check if events are fired? I was looking for something like Assert.Raise or Assert.Fire but there's nothing. Google was not very useful, most of the hits were suggestions like foo.myEvent += new EventHandler(bar); Assert.NotNull(foo.myEvent); but that proves nothing.

Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

献世佛 2024-09-19 19:36:50

检查事件是否被触发可以通过订阅该事件并设置布尔值来完成:

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

...

Assert.IsTrue(wasCalled);

由于请求 - 没有 lambda:

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

...

Assert.IsTrue(wasCalled);

Checking if events were fired can be done by subscribing to that event and setting a boolean value:

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

...

Assert.IsTrue(wasCalled);

Due to request - without lambdas:

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

...

Assert.IsTrue(wasCalled);
溺深海 2024-09-19 19:36:50

我更喜欢这样做:

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

优点:支持多线程场景(如果在不同线程中调用处理程序)

I prefer to do as follows:

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

Advantages: Supports multithreading scenario (if handler is invoked in different thread)

简单爱 2024-09-19 19:36:50

如果您知道事件将同步触发:

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));

但是,有人说应该避免测试异步行为。

If you know the event will be fired synchronously:

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

customer.Name = "Sam";

Assert.IsTrue(eventRaised);

If the event may be fired asynchronously:

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));

However, some say testing asynchronous behavior should be avoided.

可爱暴击 2024-09-19 19:36:50

我最近不得不这样做,下面是我的想法。我没有按照其他帖子所说的去做的原因是我不喜欢变量保持状态并必须在多个事件之间“手动”重置它的想法。

下面是在 MyTests 测试中测试的带有 NameChanged 事件的 ClassUnderTest 代码:

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 一起使用或扩展到其他委托。事件测试可以嵌套。

/// <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));
        };
    }
}

I recently had to do this, and below is what I came up with. The reason I did not do what the other posts said, is I do not like the idea of a variable keeping state and having to reset it "manually" between multiple events.

Below is the code of the ClassUnderTest with NameChanged event that is tested in MyTests tests:

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);
    }
}

The supporting classes are below. The classes can be used with any EventHandler<TEventArgs> or expanded to other delegates. Event tests can be nested.

/// <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));
        };
    }
}
清音悠歌 2024-09-19 19:36:50

使用 NUnit 和 Moq,您可以进行更强大的事件测试。

用于监视事件触发器的模拟类:

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);

}

Using NUnit and Moq you can do more robust event testing.

Mock Class used to monitor event triggers:

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

Setup for event Triggers:

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

Using Mock Objects in Tests:

[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);

}
吃素的狼 2024-09-19 19:36:50

我只会将 FluentAssertions 与 Nunit 一起使用: https://fluentassertions.com/eventmonitoring/ 它效果非常好。这是文档中的示例

var subject = new EditCustomerViewModel();
using (var monitoredSubject = subject.Monitor())
{
    subject.Foo();
    monitoredSubject.Should().Raise("NameChangedEvent");
}

I would just use FluentAssertions along with Nunit : https://fluentassertions.com/eventmonitoring/ it works really well. Here is an example from the docs

var subject = new EditCustomerViewModel();
using (var monitoredSubject = subject.Monitor())
{
    subject.Foo();
    monitoredSubject.Should().Raise("NameChangedEvent");
}
云柯 2024-09-19 19:36:50

您可以添加自定义事件处理程序,例如,增加测试用例类中的某些整数字段。然后检查字段是否增加。

You can add your custom event handler which, for example, increments some integer field in test case class. And then check if field was incremented.

情未る 2024-09-19 19:36:50

未经实战测试,可能无法在各种情况下工作(例如,委托的返回类型不是 void)。此外,它需要显式指定委托类型。最后,它不能/不测试调用事件处理程序所使用的实际参数。但我仍然想分享它,以防有人觉得它有用:

public static class Event
{
    public static THandler Monitored<THandler>(out IMonitor monitor) where THandler : Delegate
    {
        var monitorImpl = new Monitor();
        monitor = monitorImpl;

        var monitorParam = Expression.Parameter(typeof(Monitor), "monitor");
        var monitorRaisedCall = Expression.Call(monitorParam, typeof(Monitor).GetMethod("Raised")!);

        var handlerMethodInfo = typeof(THandler).GetMethod("Invoke")!;
        var handlerParameterExpressions = handlerMethodInfo.GetParameters()
            .Select(p => p.ParameterType)
            .Select(Expression.Parameter)
            .ToArray();

        var handlerLambda = Expression.Lambda<THandler>(monitorRaisedCall, handlerParameterExpressions);

        var factory = Expression.Lambda<Func<Monitor, THandler>>(handlerLambda, monitorParam).Compile();
        return factory(monitorImpl);
    }

    public interface IMonitor
    {
        public bool IsRaised { get; }
    }
    
    private class Monitor : IMonitor
    {
        private bool isRaised;

        public bool IsRaised
        {
            get
            {
                var result = isRaised;
                isRaised = false;
                return result;
            }
        }

        public void Raised()
        {
            isRaised = true;
        }
    }
}

在这里,我们实际上创建了一个模拟方法,其作用类似于 THandler 类型的事件处理程序。它是通过以下步骤完成的:

  • 定义一个类似于 () => 的 lambda { Monitor.Raished() } 并且如果其返回类型为 void 则符合 THandler
  • 然后,定义另一个 lambda,例如 (IMonitor monitor) = >
  • 最后,我们用实际的监视器实例编译并调用这个 lambda(代码中称为工厂)

,用法如下:

delegate void DelA(int p1, int p2);
delegate void DelB(bool p1, string p2, int p3);

void MyTest()
{
    var something = // ...
    something.A += Event.Monitored<DelA>(out var a);
    something.B += Event.Monitored<DelB>(out var b);
    // ...
    Assert.That(a.IsRaised);
    Assert.That(!b.IsRaised);
}

一个 .NetFiddle 示例(不带NUnit): https://dotnetfiddle.net/RRwqAS

Not battle-tested and might not work in various scenarios (e.g. the delegate has a return type other than void). Also, it requires to explicitly specify the delegate type. Lastly, it cannot/does not test the actual arguments the event handler was called with. But still, I'd like to share it, in case someone find it useful:

public static class Event
{
    public static THandler Monitored<THandler>(out IMonitor monitor) where THandler : Delegate
    {
        var monitorImpl = new Monitor();
        monitor = monitorImpl;

        var monitorParam = Expression.Parameter(typeof(Monitor), "monitor");
        var monitorRaisedCall = Expression.Call(monitorParam, typeof(Monitor).GetMethod("Raised")!);

        var handlerMethodInfo = typeof(THandler).GetMethod("Invoke")!;
        var handlerParameterExpressions = handlerMethodInfo.GetParameters()
            .Select(p => p.ParameterType)
            .Select(Expression.Parameter)
            .ToArray();

        var handlerLambda = Expression.Lambda<THandler>(monitorRaisedCall, handlerParameterExpressions);

        var factory = Expression.Lambda<Func<Monitor, THandler>>(handlerLambda, monitorParam).Compile();
        return factory(monitorImpl);
    }

    public interface IMonitor
    {
        public bool IsRaised { get; }
    }
    
    private class Monitor : IMonitor
    {
        private bool isRaised;

        public bool IsRaised
        {
            get
            {
                var result = isRaised;
                isRaised = false;
                return result;
            }
        }

        public void Raised()
        {
            isRaised = true;
        }
    }
}

Here, we actually create a mock method that acts like an event handler for type THandler. It is done by the following steps:

  • define a lambda that looks like (<THandler params>) => { monitor.Raised() } and this conforms to THandler if its return type is void
  • then, define another lambda like (IMonitor monitor) => <previous THandler lambda>
  • finally, we compile and call this lambda (called factory in the code) with the actual monitor instance

And the usage is like this:

delegate void DelA(int p1, int p2);
delegate void DelB(bool p1, string p2, int p3);

void MyTest()
{
    var something = // ...
    something.A += Event.Monitored<DelA>(out var a);
    something.B += Event.Monitored<DelB>(out var b);
    // ...
    Assert.That(a.IsRaised);
    Assert.That(!b.IsRaised);
}

A .NetFiddle example (without NUnit): https://dotnetfiddle.net/RRwqAS

不交电费瞎发啥光 2024-09-19 19:36:50

我自己并没有真正做到这一点,但也许您可以向您想要订阅的事件添加一个虚拟事件处理程序,并让它更新本地布尔变量,以便在该方法被触发后您可以检查该布尔值的状态以查看该事件是否被解雇了?

像这样的东西:

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

Assert.IsTrue(eventFired);

Not really done this myself, but maybe you could add a dummy event handler to the event you wanna subscribe to and have it update a local boolean variable so that after the method is fired you can check the state of that boolean to see if the event was fired?

Something like:

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

Assert.IsTrue(eventFired);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文