Mock HttpContext.Current в методе тестирования Init
Я пытаюсь добавить модульное тестирование в приложение ASP.NET MVC, которое я создал. В моих модульных тестах я использую следующий код:
[TestMethod]
public void IndexAction_Should_Return_View() {
var controller = new MembershipController();
controller.SetFakeControllerContext("TestUser");
...
}
С помощью следующих помощников, чтобы высмеять контекст контроллера:
public static class FakeControllerContext {
public static HttpContextBase FakeHttpContext(string username) {
var context = new Mock<HttpContextBase>();
context.SetupGet(ctx => ctx.Request.IsAuthenticated).Returns(!string.IsNullOrEmpty(username));
if (!string.IsNullOrEmpty(username))
context.SetupGet(ctx => ctx.User.Identity).Returns(FakeIdentity.CreateIdentity(username));
return context.Object;
}
public static void SetFakeControllerContext(this Controller controller, string username = null) {
var httpContext = FakeHttpContext(username);
var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), controller);
controller.ControllerContext = context;
}
}
Этот класс тестов наследуется от базового класса, который имеет следующее:
[TestInitialize]
public void Init() {
...
}
Внутри этого метода он вызывает библиотеку (которой я не контролирую), которая пытается запустить следующий код:
HttpContext.Current.User.Identity.IsAuthenticated
Теперь вы можете увидеть проблему. Я установил поддельный HttpContext для контроллера, но не в этом методе Init. Модульное тестирование/издевательство для меня очень новое, поэтому я хочу убедиться, что я прав. Каков правильный способ для Mock out HttpContext, чтобы он делился между моим контроллером и любыми библиотеками, которые вызываются в моем методе Init.
Ответы
Ответ 1
HttpContext.Current
возвращает экземпляр System.Web.HttpContext
, который не расширяет System.Web.HttpContextBase
. HttpContextBase
был добавлен позже, чтобы адресовать HttpContext
было сложно издеваться. Эти два класса в основном не связаны (HttpContextWrapper
используется в качестве адаптера между ними).
К счастью, HttpContext
сам является поддельным, достаточно, чтобы вы заменили IPrincipal
(Пользователь) и IIdentity
.
Следующий код работает, как ожидалось, даже в консольном приложении:
HttpContext.Current = new HttpContext(
new HttpRequest("", "http://tempuri.org", ""),
new HttpResponse(new StringWriter())
);
// User is logged in
HttpContext.Current.User = new GenericPrincipal(
new GenericIdentity("username"),
new string[0]
);
// User is logged out
HttpContext.Current.User = new GenericPrincipal(
new GenericIdentity(String.Empty),
new string[0]
);
Ответ 2
Ниже Test Init также выполнит эту работу.
[TestInitialize]
public void TestInit()
{
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
YourControllerToBeTestedController = GetYourToBeTestedController();
}
Ответ 3
Я знаю, что это более старая тема, однако Mocking приложение MVC для модульных тестов - это то, что мы делаем на регулярной основе.
Я просто хотел добавить свой опыт. Издевательство над приложением MVC 3 с помощью Moq 4 после обновления до Visual Studio 2013. Ни один из модульных тестов не работал в режиме отладки, а HttpContext показывал "не мог оценить выражение" при попытке заглянуть при переменных.
Оказывается, в visual studio 2013 есть проблемы с оценкой некоторых объектов. Чтобы снова отлаживать отлаженные веб-приложения, я должен был проверить "Использовать управляемый режим совместимости" в "Инструменты" > "Параметры" > "Отладка" > "Общие настройки".
Обычно я делаю что-то вроде этого:
public static class FakeHttpContext
{
public static void SetFakeContext(this Controller controller)
{
var httpContext = MakeFakeContext();
ControllerContext context =
new ControllerContext(
new RequestContext(httpContext,
new RouteData()), controller);
controller.ControllerContext = context;
}
private static HttpContextBase MakeFakeContext()
{
var context = new Mock<HttpContextBase>();
var request = new Mock<HttpRequestBase>();
var response = new Mock<HttpResponseBase>();
var session = new Mock<HttpSessionStateBase>();
var server = new Mock<HttpServerUtilityBase>();
var user = new Mock<IPrincipal>();
var identity = new Mock<IIdentity>();
context.Setup(c=> c.Request).Returns(request.Object);
context.Setup(c=> c.Response).Returns(response.Object);
context.Setup(c=> c.Session).Returns(session.Object);
context.Setup(c=> c.Server).Returns(server.Object);
context.Setup(c=> c.User).Returns(user.Object);
user.Setup(c=> c.Identity).Returns(identity.Object);
identity.Setup(i => i.IsAuthenticated).Returns(true);
identity.Setup(i => i.Name).Returns("admin");
return context.Object;
}
}
И запустив такой контекст
FakeHttpContext.SetFakeContext(moController);
И вызов метода прямо в контроллере
long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);
Ответ 4
Если ваша сторонняя сторонняя программа перенаправляет внутренне, так что лучше подделать HttpContext ниже:
HttpWorkerRequest initWorkerRequest = new SimpleWorkerRequest("","","","",new StringWriter(CultureInfo.InvariantCulture));
System.Web.HttpContext.Current = new HttpContext(initWorkerRequest);
System.Web.HttpContext.Current.Request.Browser = new HttpBrowserCapabilities();
System.Web.HttpContext.Current.Request.Browser.Capabilities = new Dictionary<string, string> { { "requiresPostRedirectionHandling", "false" } };