Как написать тесты для ASP.NET MVC 3 AsyncControllers с MSpec
Я хочу написать TaskController
для приложения ASP.NET MVC 3 для некоторых долгосрочных задач, таких как рассылка новостей пользователям сайта. Я думал, используя 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
}
}
Как можно написать спецификации для AsyncControllers? Представьте, что я начну со следующей спецификации:
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
?
1 ответ
Ну, я наконец-то понял, по крайней мере, как это сделать. Я уверен, что есть другие и, возможно, лучшие способы, но здесь идет:
Объявить AutoResetEvent
который будет использоваться в качестве ручки ожидания и EventHandler
это будет настроено на срабатывание после завершения асинхронного действия. Тогда в Because
блок (соответствующий Act
часть модульного тестирования) вызов WaitOne
на 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();
Это также должно работать, если вы пишете модульные тесты с NUnit или любой другой тестовой средой вместо MSpec.