-
-
Notifications
You must be signed in to change notification settings - Fork 794
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't raise non-virtual async event #977
Comments
I assume it has something to do with the fact the event needs a mockBase.Raise(b => b.SomethingHappenedAsync += a => Task.CompletedTask, new object[] { eventArgs }); ... but that fails with an
|
Also, I tried making the event hander method |
Not much time to study your issue in details tonight, just this for now: "Async events" probably make just as little sense as "async properties". While it is technically possible for a property to return a The patterns for events are even stricter than those for regular properties: The event delegate signature should have a So it should come as no surprise that Moq doesn't cater for these scenarios. |
I agree that it's not a common pattern -- but the 3rd-party class I'm dealing with is actually the latest (v5) client for Azure EventHubs from Microsoft: (decompiled) namespace Azure.Messaging.EventHubs
{
public class EventProcessorClient
{
...
//
// Summary:
// The event to be raised once event processing stops for a given partition.
public event Func<PartitionClosingEventArgs, Task> PartitionClosingAsync;
//
// Summary:
// The event to be raised just before event processing starts for a given partition.
public event Func<PartitionInitializingEventArgs, Task> PartitionInitializingAsync;
//
// Summary:
// The event responsible for processing unhandled exceptions thrown while this processor
// is running. Implementation is mandatory.
public event Func<ProcessErrorEventArgs, Task> ProcessErrorAsync;
//
// Summary:
// The event responsible for processing events received from the Event Hubs service.
// Implementation is mandatory.
public event Func<ProcessEventArgs, Task> ProcessEventAsync;
...
}
} I'd really like to be able to unit test my event handlers (in addition to integration tests). Anything you can give me would be helpful. |
Oh boy... either my .NET-fu is getting out of date, or that's a terrible API. I wonder how one is supposed to use it safely. In the short term, I believe you'd be best off testing this particular case without Moq, i.e. by writing a test double manually. Longer term, it might make sense to at least fix the misleading error message that you got. If we can easily get this scenario working as a side effect of that, all the better. But beyond that, this seems like too much of an edge case to me to warrant any new APIs (but I could be wrong). |
As it turns out, this scenario already works just fine (except for the fact that there's no way to [Fact]
public void Test()
{
var eventArgs = new CustomEventArgs();
var mockBase = new Mock<BaseClass>(MockBehavior.Strict);
var subject = new MyClass(mockBase.Object);
mockBase.Raise(b => b.SomethingHappenedAsync += null, new object[] { eventArgs });
Assert.True(eventArgs.Used);
}
public class CustomEventArgs : EventArgs
{
public bool Used { get; set; }
}
public class BaseClass
{
public virtual event Func<CustomEventArgs, Task> SomethingHappenedAsync;
}
public class MyClass
{
private readonly BaseClass _base;
public MyClass(BaseClass @base)
{
_base = @base;
_base.SomethingHappenedAsync += DoSomething;
}
private Task DoSomething(CustomEventArgs args)
{
args.Used = true;
return Task.CompletedTask;
}
} That test passes just fine. |
You know what I think it is,
Which, interestingly, isn't the same as the first error I was getting - which I would have expected here. Instead, this is similiar to the second error I got, when I tried plugging in a dummy event handler in the call to |
Been a while since I've worked very heavily with events much, but it's all coming screaming back to me that you can't actually declare an event in a base class, and then raise it from a derived class. Microsoft says not to use the Since we can't add that method to Microsoft's class, we can't actually raise the event at all. Maybe a better error message is the best Moq can do. Thanks for your help, I guess I'll have to find a different solution. |
I used SetupProperty and then Invoke on the Object: public delegate Task AwaitableAction();
public interface IClassToMock
{
AwaitableAction SomethingChanged { get; set; } // async event
}
[Test]
public async Task ClassTest()
{
var mock = new Mock<IClassToMock>();
mock.SetupProperty(x => x.SomethingChanged);
var testClass = new TestClass(mock.Object);
await mock.Object.SomethingChanged.Invoke().ConfigureAwait(false);
Assert.That(testClass.SomethingHappened, Is.True);
} |
I stumbled upon this issue (here in github) because I was facing something similar. RabbitMQ has its own async events: https://rabbitmq.github.io/rabbitmq-dotnet-client/api/RabbitMQ.Client.Events.AsyncEventHandler-1.html Most of the Rabbit API is consumed via async events and it makes it very easy to adopt it into a typical async/await ecosystem. In the world of async/await, it makes sense that async events are becoming a thing--too bad Microsoft hasn't made them a first-class-citizen yet. :/ In the interim I had to revert back to synchronous events so that I could test things appropriately. |
I'm reopening this as a reminder to myself to look into RabbitMQ's async events. My current thinking is still that async events don't make sense in principle, but perhaps things have moved on and I am now mistaken. Will take a look as soon as I find some spare time. |
FWIW; I.e. var myMock = new Mock<IInterface>();
Func<Task> ev = null;
myMock
.SetupAdd(x => x.SomethingHappenedAsync += It.IsAny<Func<Task>>())
.Callback((Func<Task> evt) => ev = evt);
// ...
await ev.Invoke(); Hope this helps |
OK, I finally took a closer look. There's several different things we could talk about. Let's look at one after the other.
Moq goes to some lengths to allow raising an event from the outside. It does so by intercepting the
So what could be done to improve Moq?
I don't think we can. Say you call
We cannot improve that error message because All of that being said, I retract my earlier statement that async events are a horrible design. Given that the event delegates used have a return type of
We could in theory add a In conclusion, there's still nothing we can do to enable |
I need Moq to raise an async event on my mock object, so I can test how my code handles it. But I can't seem to get Moq to do it - either it's not supported, or I can't find the syntax anywhere.
The setup:
I'm using a 3rd-party class (can't change it) with an async event defined on it:
I have my own class which encapsulates a
BaseClass
and attaches to its events:In my tests, I'm mocking the
BaseClass
, passing it to my constructor, and now I want to raise the event to test how my code handles it:Instead, the lambda inside the call to
Raise(...)
throws anArgumentNullException
:Is this a supported feature?
The text was updated successfully, but these errors were encountered: