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.
精彩评论