Макет HttpContext.Current у методі тестування Init


177

Я намагаюся додати модульне тестування до вбудованої програми 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. Тестування / глузування підрозділів для мене дуже нове, тому я хочу переконатися, що я правильно це зрозумів. Який для мене правильний спосіб знущатися з HttpContext, щоб він був спільним для мого контролера та будь-яких бібліотек, які викликаються в моєму методі Init.

Відповіді:


362

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

Привіт, але як я можу встановити це для користувача, який вийшов із системи?
nfplee

5
@nfplee - Якщо ви передасте порожній рядок у GenericIdentityконструктор, IsAuthenticatedповернеться помилковим
Річард Салай

2
Чи можна це використовувати для знущання з кешу в HttpContext?
DevDave

1
Так, це могло б. Дякую!
DevDave

4
@CiaranG - MVC використовує HttpContextBase, з яких можна глузувати . Немає необхідності використовувати обхідне рішення, яке я опублікував, якщо ви використовуєте MVC. Якщо ви продовжуєте це, вам, ймовірно, потрібно запустити код, який я опублікував, перш ніж навіть створити контролер.
Річард Шалай

35

Нижче тест Init також зробить цю роботу.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
  YourControllerToBeTestedController = GetYourToBeTestedController();
}

Я не можу отримати адреса до HTTPContext в окремому тестовому проекті в своєму рішенні. Чи можете ви отримати це через спадкування контролера?
Джон Петерс

1
Ви маєте посилання на System.Webсвій тестовий проект?
ПУГ

Так, але мій проект - проект MVC, чи можливо, що версія MVC System.Web містить лише підмножину цього простору імен?
Джон Петерс

2
@ user1522548 (ви повинні створити обліковий запис) Assembly System.Web.dll, v4.0.0.0 безумовно має HTTPContext, я щойно перевірив свій вихідний код.
ПУГ

Моя помилка, я маю посилання у файлі на System.Web.MVC та NOT System.Web. Спасибі за вашу допомогу.
Джон Петерс

7

Я знаю, що це старіша тема, однак знущання над програмою MVC для одиничних тестів - це те, що ми робимо на регулярній основі.

Я просто хотів додати свій досвід Переслідування програми MVC 3 за допомогою Moq 4 після оновлення до Visual Studio 2013. Жоден з тестових модулів не працював у режимі налагодження, і HttpContext показував "не міг оцінити вираз", намагаючись зазирнути до змінних .

Виявляється, візуальна студія 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);

Чи є вагомі підстави встановити це таким чином проти прийнятої відповіді? Це не виглядає складніше і, здається, не дає додаткової користі.
kilkfoe

1
Він пропонує більш детальний / модульний метод глузування
Вінсент Бускарелло

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" } };
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.