0

It seems that when an exception is raised inside an event raise, it gets caught by Moq and it goes undetected. Might have to do with it being async? (Getting caught inside Task.Exception).

Example:

internal class Program
{
   static async Task Main(string[] args)
   {
       var eventManager = new EventManager();
       var eventConsumer = new EventConsumer(eventManager);

       await eventManager.OnEventHappened(1000);

       Console.WriteLine("Unreacheble, exception thrown");
   }
}

public delegate Task CustomEvent(int number);

public interface IEventManager
{
   event CustomEvent CustomEvent;
}

public class EventConsumer
{
   private readonly IEventManager _eventManager;

   public EventConsumer(IEventManager eventManager)
   {
       _eventManager = eventManager;

       _eventManager.CustomEvent += _eventManager_CustomEvent;
   }

   private async Task _eventManager_CustomEvent(int number)
   {
       await Task.Delay(number);
       throw new NotImplementedException();
   }
}

public class EventManager : IEventManager
{
   public event CustomEvent CustomEvent;

   public async Task OnEventHappened(int number)
   {
       await CustomEvent.Invoke(number);
   }
}

For instance, these tests are failing (expected exception but was null).

[Test]
public void ShouldThrowException()
{
    var eventManagerMock = new Mock<IEventManager>(MockBehavior.Strict);
    var consumer = new EventConsumer(eventManagerMock.Object);

    Assert.Throws<NotImplementedException>(() => eventManagerMock.Raise(x => x.CustomEvent += null, 1000));
}

[Test]
public void ShouldThrowException2()
{
    var eventManagerMock = new Mock<IEventManager>(MockBehavior.Strict);
    var consumer = new EventConsumer(eventManagerMock.Object);

    eventManagerMock.Setup(x => x.OnEventHappened(1000)).Returns(Task.CompletedTask).Raises(x => x.CustomEvent += null, 1000);

    Assert.ThrowsAsync<NotImplementedException>(() => eventManagerMock.Object.OnEventHappened(1000));
}

Am I doing something wrong or is it a bug indeed?

I have opened an issue on moq's repro, but got no response there so far.

Thanks!

Matheus Lemos
  • 578
  • 4
  • 13
  • Did you confirm the test execution reaches the place where the exception is thrown? As far as I can see is that you are mocking an interface but you did not configure the behavior of that Mock. So effectively it does nothing. – L01NL Jul 19 '23 at 08:19
  • Yes, if I place a breakpoint at _eventManager_CustomEvent, it reaches the method, but it interrupts execution after stepping over Task.Delay. So my guess is that the exception is getting caught and encapsulated somewhere. – Matheus Lemos Jul 19 '23 at 08:38
  • Have you enabled catching exception while debugging so to see better what is actually happening? – Ralf Jul 19 '23 at 08:44
  • 2
    https://github.com/moq/moq/pull/1313 and https://github.com/moq/moq/issues/977 talk a bit of the problems. There should be a RaiseAsync as of version 4.19. – Ralf Jul 19 '23 at 08:47
  • 1
    Asynchronous event handlers have a signature of `async void` which means they can't be awaited. I'm surprised `await CustomEvent.Invoke(number);` compiles at all. Without awaiting, exceptions are lost. Event handlers *shouldn't* throw anyway, as this will terminate the application. In a desktop application most events are raised by the runtime which *doesn't* handle such exceptions – Panagiotis Kanavos Jul 19 '23 at 08:49
  • Yes, the idea is to terminate the application on such exception. I used a custom delegate with the async Task signature. I couldn't find those issues Ralf mentioned, maybe I need to improve my gitthub search skills :p I also could not find 4.19 though, I guess it's not general release yet. – Matheus Lemos Jul 19 '23 at 09:03
  • 1
    I suspect `event` is the wrong feature to use here. Events were created for desktop applications, 10 years before `async/await` was introduced. If you want to create a class that handles business events it's better to store callbacks with the signature you want, eg `Action` – Panagiotis Kanavos Jul 19 '23 at 09:06
  • Yes, I agree, but that is my use case. I'm creating a wrapper around SystemEvents from Microsoft.Win32, so I have not much of a choice other than "cascading" the event. – Matheus Lemos Jul 19 '23 at 09:13
  • Does this answer your question? [xunit Assert.ThrowsAsync() does not work properly?](https://stackoverflow.com/questions/40828272/xunit-assert-throwsasync-does-not-work-properly) – Aluan Haddad Jul 19 '23 at 17:38
  • No, NUnit ThrowsAsync is not awaitable, that is not the issue. Other places using ThrowsAsync are working fine. Thanks, though! – Matheus Lemos Jul 20 '23 at 09:01

1 Answers1

0

It does seem to be a problem solvable by the RaiseAsync method but, at the time of this writing, it's not yet released.

I'll update this answer once it is released and confirmed to solve the issue.

https://github.com/moq/moq/blob/main/CHANGELOG.md

Matheus Lemos
  • 578
  • 4
  • 13