anhoppe
anhoppe

Reputation: 4497

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

Upvotes: 10

Views: 10838

Answers (2)

k.m
k.m

Reputation: 31454

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.

Upvotes: 9

Michal Ciechan
Michal Ciechan

Reputation: 13888

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

Upvotes: 6

Related Questions