Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Raise an EventHandler<TEventArgs> event with a Moq instance

I have the interfaces

public interface IBar
{

}

and

public interface IFoo
{
    event EventHandler<IBar> MyEvent;
}

and a class

public class Foobar
{
    public Foobar(IFoo foo)
    {
        foo.MyEvent += MyEventMethod;
    }

    private void MyEventMethod(object sender, IBar bar)
    {
        // do nothing
    }
}

Now I want to unit test this brilliant piece of code using Moq 4:

[Test]
public void MyTest()
{
    Mock<IFoo> foo = new Mock<IFoo>();
    Mock<IBar> bar = new Mock<IBar>();

    Foobar foobar = new Foobar(foo.Object);

    foo.Raise(e => e.MyEvent += null, bar.Object);
}

From my understanding Foobar.MyEventMethod should be called through the raise. What happens is that I get a runtime exception that says System.Reflection.TargetParameterCountEception {"Parameter count mismatch."}.

Funny thing: when I Raise the following in the unit test:

foo.Raise(e => e.MyEvent += null, EventArgs.Empty, bar.Object);

Everything works as I want it. Can anybody explain why three arguments are needed for the call?

Thank you

like image 999
anhoppe Avatar asked Mar 19 '15 14:03

anhoppe


People also ask

How do you mock an event handler?

Mocks either substitute the event source by using . Raise(), or they substitute an object that will consume another class under test's event (to assert the event was raised), in which case you use . Callback() to record "handling" the event in a local flag variable.

Is an EventHandler a delegate?

The EventHandler delegate is a predefined delegate that specifically represents an event handler method for an event that does not generate data. If your event does generate data, you must use the generic EventHandler<TEventArgs> delegate class.


2 Answers

I assume you use .NET 4.5 then. Type constraint was removed from EventHandler<TEventArgs> which allows you to do something like this:

event EventHandler<IBar> MyEvent;

Where IBar is just some interface.

IN 4.0, with constraint restricting TEventArgs to be assignable to EventArgs type, your code wouldn't compile.

As a result of this (IBar not deriving from EventArgs), Moq doesn't consider your event as "corresponding to Event Handler pattern", and treats it as any other delegate:

// Raising a custom event which does not adhere to the EventHandler pattern
...
// Raise passing the custom arguments expected by the event delegate
mock.Raise(foo => foo.MyEvent += null, 25, true);

Which means you have to provide all parameters, including sender.

like image 110
k.m Avatar answered Nov 10 '22 15:11

k.m


The reason the first is not working because EventHandlers have 2 parameters (object sender, EventArgs args).

When you are setting up mocking

foo.Raise(e => e.MyEvent += null, EventArgs.Empty, bar.Object);

thee => e.MyEvent += null is an expression to tell Moq which event to raise,

The following 2 parameters are the 2 arguments you want to raise it with.

EventArgs.Empty, bar.Object

Note: If memory serves me right, those should be the other way around.

When you try to raise an event with 1 argument (bar.Object) Moq throws an exception saying that event handler requires 2 as it uses reflection to invoke it.

Your first case could be written like this:

public class Foo : IFoo
{
    public event EventHandler<IBar> MyEvent;

    public void OnMyEvent(IBar bar)
    {
        MyEvent(EventArgs.Empty)
    }
}

Which gives you a compiler error: Delegate 'EventHandler' does not take 1 arguments

So that's why you need 2 parameters, as you would invoke it with the following:

public class Foo : IFoo
{
    public event EventHandler<IBar> MyEvent;

    public void OnMyEvent(IBar bar)
    {
        MyEvent(this, bar);
    }
}
like image 44
Michal Ciechan Avatar answered Nov 10 '22 14:11

Michal Ciechan