开发者

How to write tests for ASP.NET MVC 3 AsyncControllers with MSpec

I want to write a TaskController for an ASP.NET MVC 3 application to some long running tasks, like sending a newsletter to the users of the site. I thought using an AsyncController would be appropriate as sending emails might take a while, and I want to be able to save some state to the database when the task finishes running.

Being the properly brought up developer that I am (:þ), and being really into BDD, I naturally want to start off with a spec using MSpec.

Imagine my controller looks like this:

public class TaskController : AsyncController
{    
    readonly ISession _session;

    public TaskController(ISession session)
    {
        _session = session;
    }

    public void SendMailAsync()
    {
        // Get emails from db and send them
    }

    public ActionResult SendMailCompleted()
    {
        // Do some stuff here
    }
}

How does one go about writing specs for AsyncControllers? Imagine I start with the following specification:

开发者_如何学Pythonpublic class TaskControllerContext
{
    protected static Mock<ISession> session;
    protected static TaskController controller;
    protected static ActionResult result;
}

[Subject(typeof(TaskController), "sending email")]
public class When_there_is_mail_to_be_sent : TaskControllerContext
{
    Establish context = () =>
    {
        session = new Mock<ISession>();
        controller = new TaskController(session.Object);
    };

    // is this the method to call?
    Because of = () => controller.SendMailAsync();

    // I know, I know, needs renaming...
    It should_send_mail;
}

Should I be calling the SendMailAsync method for the test? I actually feels yucky. How do I deal with the result from SendMailCompleted?


Well, I finally figured out at least a way to do it. I'm sure there are other and probably better ways, but here goes:

Declare an AutoResetEvent that will be used as wait handle and an EventHandler that will be set up to fire when the async action completes. Then in the Because block (corresponding to the Act part of unit testing) call WaitOne on the AutoResetEvent:

static AutoResetEvent waitHandle; 
static EventHandler eventHandler;
static int msTimeout = 5000;
static IEnumerable<Email> response;

Establish context = () =>
{
    toSend = new List<Email>
    {
        // Emails here
    };

    // Prepare the mock objects
    session.Setup(x => x.All<Email>()).Returns(toSend.AsQueryable());
    mailer.Setup(x => x.SendMail(Moq.It.IsAny<IEnumerable<Email>>()))
        .Returns(toSend);

    // Set up the wait handle    
    waitHandle = new AutoResetEvent(false);
    eventHandler = (sender, e) => waitHandle.Set();
    controller.AsyncManager.Finished += eventHandler;
};

Because of = () =>
{
    controller.SendMailAsync();
    if (!waitHandle.WaitOne(msTimeout, false)) {}

    response = (IEnumerable<Email>) controller.AsyncManager
        .Parameters["sentMails"];
};

It should_send_mail = () => response.Any().ShouldBeTrue();

This should work as well if you're writting unit tests with NUnit or any other test framework instead of MSpec.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜