nsubstitute / NSubstitute

A friendly substitute for .NET mocking libraries.

Home Page:https://nsubstitute.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Provide a way to pass null for custom event args so that Raise.EventWith can be used to raise events for custom event args without a default constructor

zhitchcock77 opened this issue · comments

Is your feature request related to a problem? Please describe.
Currently if we want to test raising an event that has custom events args (rather than just using EventArgs), the custom event args class must have a default constructor or some method to create the object. This is because NSubstitute will attempt to create an instance of the event args if we provide null (see code here) when calling Raise.EventWith.

However, it is not necessary for a class to have a public constructor (e.g. constructor could be internal), and therefore if the custom event args class doesn't have a publicly accessible constructor, there is no way to raise the event using Raise.EventWith

Describe the solution you'd like
It would be great if there was a way that Raise.EventWith could be called where null can be provided for the custom event arg, and NSubstitue does not attempt to create an instance of the custom event arg.

One possible solution is to not throw the exception if the call to GetDefaultConstructor returns null, and in that case, just return null from the GetDefaultForEventArgType method.

Additional context
Below is some example code demonstrating this problem:

namespace NSubstituteEventArgs
{
    public class CustomEventArgs : EventArgs
    {
        internal CustomEventArgs() { }
    }

    public interface IExample
    {
        public event EventHandler<CustomEventArgs> MyEvent;

        public void DoSomethingToTriggerMyEvent();

    }
    
    internal class Example : IExample
    {
        public event EventHandler<CustomEventArgs>? MyEvent;

        public void DoSomethingToTriggerMyEvent()
        {
            Console.WriteLine("DoSomethingToTriggerMyEvent called in Example");
            OnMyEvent(new CustomEventArgs());
        }

        private void OnMyEvent(CustomEventArgs e)
        {
            MyEvent?.Invoke(this, e);
        }
    }

    public class Consumer
    {
        private readonly IExample _example;

        public Consumer(IExample example)
        {
            _example = example;
            _example.MyEvent += _example_MyEvent;
        }

        private void _example_MyEvent(object? sender, CustomEventArgs e)
        {
            DoSomething();
            Console.WriteLine("MyEvent handled by Consumer");
        }

        public void DoSomething()
        {
            Console.WriteLine("DoSomething method called in Consumer");
        }
    }
}

And the unit test below will fail with the message: NSubstitute.Exceptions.CannotCreateEventArgsException : Cannot create CustomEventArgs for this event as it has no default constructor. Provide arguments for this event by calling Raise.EventWith(CustomEventArgs).

  [Fact]
  public void DoSomething_WhenMyEventIsRaised_IsCalled()
  {
      // Arrange
      IExample mockExample = Substitute.For<IExample>();
      var consumer = new Consumer(mockExample);

      // Act
      mockExample.MyEvent += Raise.EventWith<CustomEventArgs>(this, (CustomEventArgs)null!);

      // Assert
      consumer.Received(1).DoSomething();
  }