如何使用 MSpec 为 ASP.NET MVC 3 AsyncController 编写测试

发布于 2024-10-21 20:26:57 字数 1414 浏览 0 评论 0原文

我想为 ASP.NET MVC 3 应用程序编写一个 TaskController 来执行一些长时间运行的任务,例如向网站用户发送新闻通讯。我认为使用 AsyncController 比较合适,因为发送电子邮件可能需要一段时间,并且我希望能够在任务运行完成时将一些状态保存到数据库中。

作为一个正确成长的开发人员(:þ),并且真正热衷于 BDD,我自然希望从使用 MSpec 的规范开始。

想象一下我的控制器如下所示:

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
    }
}

如何为 AsyncController 编写规范?想象一下,我从以下规范开始:

public 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;
}

我应该调用 SendMailAsync 方法进行测试吗?我其实感觉很恶心。 如何处理 SendMailCompleted 的结果?

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:

public 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

九歌凝 2024-10-28 20:26:57

好吧,我终于找到了至少一种方法。我确信还有其他可能更好的方法,但这里是:

声明一个将用作等待句柄的 AutoResetEvent 和一个将被设置为触发的 EventHandler当异步操作完成时。然后在 Because 块(对应于单元测试的 Act 部分)中对 AutoResetEvent 调用 WaitOne

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();

:如果您使用 NUnit 或任何其他测试框架而不是 MSpec 编写单元测试,应该也可以工作。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文