diff --git a/src/NSubstitute/Core/WhenCalled.cs b/src/NSubstitute/Core/WhenCalled.cs index 3fa65570..8d956f60 100644 --- a/src/NSubstitute/Core/WhenCalled.cs +++ b/src/NSubstitute/Core/WhenCalled.cs @@ -1,3 +1,4 @@ +using System.Threading.Tasks; using NSubstitute.Routing; namespace NSubstitute.Core; @@ -19,6 +20,15 @@ public void Do(Action callbackWithArguments) } /// + /// Perform this action when called. + /// + /// + public void Do(Func callbackWithArguments) + { + Do(callInfo => callbackWithArguments(callInfo).GetAwaiter().GetResult()); + } + + /// /// Perform this configured callback when called. /// /// diff --git a/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs b/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs index 95bea67b..76988cfd 100644 --- a/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs +++ b/src/NSubstitute/Routing/Handlers/RaiseEventHandler.cs @@ -1,4 +1,5 @@ using System.Reflection; +using System.Threading.Tasks; using NSubstitute.Core; using NSubstitute.Exceptions; @@ -26,7 +27,7 @@ public RouteAction Handle(ICall call) try { - handler.DynamicInvoke(eventArguments); + (handler.DynamicInvoke(eventArguments) as Task)?.GetAwaiter().GetResult(); } catch (TargetInvocationException e) { diff --git a/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/AsyncEventHandlersWithNSubstituteTests.cs b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/AsyncEventHandlersWithNSubstituteTests.cs new file mode 100644 index 00000000..99eb582a --- /dev/null +++ b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/AsyncEventHandlersWithNSubstituteTests.cs @@ -0,0 +1,104 @@ +using NUnit.Framework; + +namespace NSubstitute.Acceptance.Specs.AsyncEventHandlers; + +[TestFixture] +public class AsyncEventHandlersWithNSubstituteTests +{ + #region Fields + + private bool _waitFinished; + + #endregion + + #region Setup and Teardown + + [SetUp] + public void SetUp() + { + _waitFinished = false; + } + + #endregion + + #region Public Methods + + [Test] + public async Task TestImplementationWithAsynchronousHandler() + { + var testImplementation = new TestImplementation(); + + testImplementation.TestEvent += AsynchronousEventHandler; + + await testImplementation.RaiseEventAsync(); + + TestContext.WriteLine("Raise.Event finished"); + + Assert.That(_waitFinished, Is.True); + } + + [Test] + public async Task TestSubstituteImplementationWithSynchronousHandler() + { + var testImplementation = new TestImplementation(); + + testImplementation.TestEvent += SynchronousEventHandler; + + await testImplementation.RaiseEventAsync(); + + TestContext.WriteLine("Raise.Event finished"); + + Assert.That(_waitFinished, Is.True); + } + + [Test] + public void TestSubstituteWithAsynchronousHandler() + { + var testInterface = Substitute.For(); + + testInterface.TestEvent += AsynchronousEventHandler; + + testInterface.TestEvent += Raise.Event(); + + TestContext.WriteLine("Raise.Event finished"); + + Assert.That(_waitFinished, Is.True); + } + + [Test] + public void TestWithSynchronousHandler() + { + var testInterface = Substitute.For(); + + testInterface.TestEvent += SynchronousEventHandler; + + testInterface.TestEvent += Raise.Event(); + + TestContext.WriteLine("Raise.Event finished"); + + Assert.That(_waitFinished, Is.True); + } + + #endregion + + #region Private Methods + + private async Task AsynchronousEventHandler() + { + TestContext.WriteLine("starting to wait"); + await Task.Delay(100); + _waitFinished = true; + TestContext.WriteLine("wait finished"); + } + + private Task SynchronousEventHandler() + { + TestContext.WriteLine("starting to wait"); + Thread.Sleep(100); + _waitFinished = true; + TestContext.WriteLine("wait finished"); + return Task.CompletedTask; + } + + #endregion +} \ No newline at end of file diff --git a/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/ITestInterface.cs b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/ITestInterface.cs new file mode 100644 index 00000000..0f0f045b --- /dev/null +++ b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/ITestInterface.cs @@ -0,0 +1,12 @@ +namespace NSubstitute.Acceptance.Specs.AsyncEventHandlers; + +public delegate Task TestEventHandler(); + +public interface ITestInterface +{ + #region Events + + event TestEventHandler TestEvent; + + #endregion +} \ No newline at end of file diff --git a/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/TestImplementation.cs b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/TestImplementation.cs new file mode 100644 index 00000000..8528b41b --- /dev/null +++ b/tests/NSubstitute.Acceptance.Specs/AsyncEventHandlers/TestImplementation.cs @@ -0,0 +1,22 @@ +namespace NSubstitute.Acceptance.Specs.AsyncEventHandlers; + +public class TestImplementation : ITestInterface +{ + #region Events + + public event TestEventHandler? TestEvent; + + #endregion + + #region Public Methods + + public async Task RaiseEventAsync() + { + if (TestEvent != null) + { + await TestEvent(); + } + } + + #endregion +} \ No newline at end of file diff --git a/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs b/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs index 60e19b1d..99e6fb05 100644 --- a/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs +++ b/tests/NSubstitute.Acceptance.Specs/WhenCalledDo.cs @@ -1,3 +1,6 @@ +using System; +using System.Threading; +using System.Threading.Tasks; using NSubstitute.Acceptance.Specs.Infrastructure; using NSubstitute.Core; using NUnit.Framework; @@ -9,11 +12,31 @@ public class WhenCalledDo { private ISomething _something; + + [Test] + public void Execute_when_called_async() + { + var called = false; + _something.When(substitute => substitute.Echo(1)).Do(async info => + { + await Task.Delay(100); + called = true; + }); + + Assert.That(called, Is.False, "Called"); + _something.Echo(1); + Assert.That(called, Is.True, "Called"); + } + [Test] public void Execute_when_called() { var called = false; - _something.When(substitute => substitute.Echo(1)).Do(info => called = true); + _something.When(substitute => substitute.Echo(1)).Do(info => + { + Thread.Sleep(100); + called = true; + }); Assert.That(called, Is.False, "Called"); _something.Echo(1);