Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Verifying Mock method was called inside Task.Run

How can I verify that a method was called on a mock when the method itself is called in a delegate passed to Task.Run? By time mock.Verify is called the Task still hasn't executed.

I have tried await Task.Delay just before mock.Verify but this seems to leave the test runner hanging.

The reason for using Task.Run is to offload the logic to prevent an attacker from being able to differentiate whether the email address exists in the system by the time to execute.

using System.Threading.Tasks;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

namespace AsyncTesting
{
    class MyController : Controller
    {
        public IEmailService EmailService { get; set; }

        public MyController(IEmailService emailService)
        {
            EmailService = emailService;
        }

        public ViewResult BeginPasswordReset(string emailAddress)
        {
            BeginPasswordResetAsync(emailAddress);

            return View();
        }

        private Task BeginPasswordResetAsync(string emailAddress)
        {
            return Task.Run(delegate
            {
                EmailService.Send(emailAddress);
            });
        }

    }

    internal interface IEmailService
    {
        void Send(string emailAddress);
    }

    internal class MyControllerTests
    {
        [TestMethod]
        public void BeginPasswordReset_SendsEmail()
        {
            var mockEmailService = new Mock<IEmailService>();
            var controller = new MyController(mockEmailService.Object);
            const string emailAddress = "[email protected]";

            controller.BeginPasswordReset(emailAddress);

            mockEmailService.Verify(es=>es.Send(emailAddress));
        }
    }
}
like image 378
R Day Avatar asked Jul 30 '15 15:07

R Day


2 Answers

In your task you could set a ManualResetEvent (which our test code blocks on using something like:

Assert.IsTrue(yourEvent.WaitForOne(TimeSpan.FromSecond(<max time you want to wait>), "the event failed to run");

like this:

public void BeginPasswordReset_SendsEmail()
{
    const string emailAddress = "[email protected]";

    ManualResetEvent sendCalled= new ManualResetEvent(false);

    var mockEmailService = new Mock<IEmailService>();
    mockEmailService.Setup(m => m.Send(emailAddress)).Callback(() =>
    {
        sendCalled.Set();
    });

    var controller = new MyController(mockEmailService.Object);

    controller.BeginPasswordReset(emailAddress);

    Assert.IsTrue(sendCalled.WaitOne(TimeSpan.FromSeconds(3)), "Send was never called");
    mockEmailService.Verify(es => es.Send(emailAddress));
}
like image 83
Robert Horvick Avatar answered Oct 03 '22 23:10

Robert Horvick


Some quick research it looks like it is possible with MSTest. e.g.

[TestMethod]
public async Task BeginPasswordResetAsync();
{
    await BeginPasswordResetAsync("emailAddress");

    mockEmailService.Verify...
}
like image 34
Padraic Avatar answered Oct 03 '22 22:10

Padraic