Mock HttpContext.Current in Test Init Method

175

Ich versuche, einer von mir erstellten ASP.NET MVC-Anwendung Komponententests hinzuzufügen. In meinen Unit-Tests verwende ich den folgenden Code:

[TestMethod]
public void IndexAction_Should_Return_View() {
    var controller = new MembershipController();
    controller.SetFakeControllerContext("TestUser");

    ...
}

Mit den folgenden Helfern, um den Controller-Kontext zu verspotten:

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

Diese Testklasse erbt von einer Basisklasse mit folgenden Eigenschaften:

[TestInitialize]
public void Init() {
    ...
}

Innerhalb dieser Methode ruft es eine Bibliothek auf (über die ich keine Kontrolle habe), die versucht, den folgenden Code auszuführen:

HttpContext.Current.User.Identity.IsAuthenticated

Jetzt können Sie wahrscheinlich das Problem sehen. Ich habe den gefälschten HttpContext gegen den Controller gesetzt, aber nicht in dieser Basis-Init-Methode. Unit Testing / Mocking ist für mich sehr neu, deshalb möchte ich sicherstellen, dass ich das richtig mache. Wie kann ich den HttpContext richtig verspotten, damit er von meinem Controller und allen Bibliotheken, die in meiner Init-Methode aufgerufen werden, gemeinsam genutzt wird?

nfplee
quelle

Antworten:

361

HttpContext.CurrentGibt eine Instanz von zurück System.Web.HttpContext, die nicht erweitert wird System.Web.HttpContextBase. HttpContextBasewurde später hinzugefügt, um HttpContextdie Schwierigkeit zu verspotten. Die beiden Klassen sind grundsätzlich nicht miteinander verbunden ( HttpContextWrapperwird als Adapter zwischen ihnen verwendet).

Glücklicherweise HttpContextist es gerade so fälschbar, dass Sie den IPrincipal(Benutzer) und ersetzen können IIdentity.

Der folgende Code wird auch in einer Konsolenanwendung wie erwartet ausgeführt:

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]
    );
Richard Szalay
quelle
Prost, aber wie könnte ich dies für einen abgemeldeten Benutzer einstellen?
Nfplee
5
@nfplee - Wenn Sie eine leere Zeichenfolge an den GenericIdentityKonstruktor übergeben, IsAuthenticatedwird false zurückgegeben
Richard Szalay
2
Könnte dies verwendet werden, um den Cache im HttpContext zu verspotten?
DevDave
1
Ja, das könnte es. Vielen Dank!
DevDave
4
@CiaranG - MVC verwendet HttpContextBase, die verspottet werden können. Wenn Sie MVC verwenden, müssen Sie die von mir veröffentlichte Problemumgehung nicht verwenden. Wenn Sie damit fortfahren, müssen Sie wahrscheinlich den von mir veröffentlichten Code ausführen, bevor Sie den Controller überhaupt erstellen .
Richard Szalay
34

Unter Test Init wird auch die Arbeit erledigen.

[TestInitialize]
public void TestInit()
{
  HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
  YourControllerToBeTestedController = GetYourToBeTestedController();
}
MOPS
quelle
Ich kann in einem separaten Testprojekt in meiner Lösung keine Adressierbarkeit für HTTPContext erhalten. Können Sie es durch Erben eines Controllers erhalten?
John Peters
1
Haben Sie System.Webin Ihrem Testprojekt einen Verweis darauf ?
PUG
Ja, aber mein Projekt ist ein MVC-Projekt. Ist es möglich, dass die MVC-Version von System.Web nur eine Teilmenge dieses Namespace enthält?
John Peters
2
@ user1522548 (Sie sollten ein Konto erstellen) Assembly System.Web.dll, v4.0.0.0 hat definitiv HTTPContext Ich habe gerade meinen Quellcode überprüft.
PUG
Mein Fehler, ich habe einen Verweis in der Datei auf System.Web.MVC und NICHT auf System.Web. Danke für Ihre Hilfe.
John Peters
7

Ich weiß, dass dies ein älteres Thema ist, aber wir verspotten eine MVC-Anwendung für Unit-Tests sehr regelmäßig.

Ich wollte nur meine Erfahrungen hinzufügen. Verspotten einer MVC 3-Anwendung mit Moq 4 nach dem Upgrade auf Visual Studio 2013. Keiner der Komponententests funktionierte im Debug-Modus, und der HttpContext zeigte an, dass beim Versuch, einen Blick auf die Variablen zu werfen, "Ausdruck konnte nicht ausgewertet werden" .

Es stellt sich heraus, dass Visual Studio 2013 Probleme bei der Bewertung einiger Objekte hat. Damit das Debuggen verspotteter Webanwendungen wieder funktioniert, musste ich unter Extras => Optionen => Debuggen => Allgemeine Einstellungen den "Verwalteten Kompatibilitätsmodus verwenden" aktivieren.

Ich mache im Allgemeinen so etwas:

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


}

Und den Kontext so initiieren

FakeHttpContext.SetFakeContext(moController);

Und die Methode im Controller direkt aufrufen

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);
Aggaton
quelle
Gibt es einen guten Grund, dies gegenüber der akzeptierten Antwort so einzustellen? Auf Anhieb scheint dies komplizierter zu sein und scheint keinen zusätzlichen Nutzen zu bieten.
Kilkfoe
1
Es bietet eine detailliertere / modularere Verspottungsmethode
Vincent Buscarello
4

Wenn Ihre Anwendung von Drittanbietern intern umgeleitet wird, ist es besser, HttpContext wie folgt zu verspotten:

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" } };
Divang
quelle