Mock HttpContext.Current nel metodo Init Test


177

Sto cercando di aggiungere test unitari a un'applicazione ASP.NET MVC che ho creato. Nei test unitari utilizzo il seguente codice:

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

    ...
}

Con i seguenti helper per deridere il contesto del controller:

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

Questa classe di test eredita da una classe di base che ha i seguenti:

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

All'interno di questo metodo chiama una libreria (di cui non ho alcun controllo) che tenta di eseguire il codice seguente:

HttpContext.Current.User.Identity.IsAuthenticated

Ora puoi probabilmente vedere il problema. Ho impostato il falso HttpContext sul controller ma non in questo metodo Init di base. Il test di unità / derisione è molto nuovo per me, quindi voglio assicurarmi di farlo bene. Qual è il modo corretto per me di deridere HttpContext in modo che sia condiviso attraverso il mio controller e tutte le librerie che sono chiamate nel mio metodo Init.

Risposte:


362

HttpContext.Currentrestituisce un'istanza di System.Web.HttpContext, che non si estende System.Web.HttpContextBase. HttpContextBaseè stato aggiunto in seguito per HttpContextessere difficile da deridere. Le due classi sono sostanzialmente indipendenti ( HttpContextWrapperviene utilizzato come adattatore tra di loro).

Fortunatamente, di per HttpContextsé è contraffatto quanto basta per sostituire IPrincipal(Utente) e IIdentity.

Il codice seguente viene eseguito come previsto, anche in un'applicazione console:

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

Saluti, ma come posso impostarlo per un utente disconnesso?
nfplee,

5
@nfplee - Se passi una stringa vuota nel GenericIdentitycostruttore, IsAuthenticatedrestituirà false
Richard Szalay il

2
Potrebbe essere usato per deridere Cache in HttpContext?
DevDave

1
Sì, potrebbe. Grazie!
DevDave

4
@CiaranG: utilizza MVC HttpContextBase, che può essere deriso. Non è necessario utilizzare la soluzione alternativa che ho pubblicato se si utilizza MVC. Se vai avanti con esso, probabilmente dovrai eseguire il codice che ho pubblicato prima ancora di creare il controller.
Richard Szalay,

35

Sotto Test Init farà anche il lavoro.

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

Non riesco a ottenere indirizzabilità a HTTPContext in un progetto di test separato nella mia soluzione. Sei in grado di ottenerlo ereditando un controller?
John Peters,

1
Hai riferimenti System.Webnel tuo progetto di test?
PUG

Sì, ma il mio progetto è un progetto MVC, è possibile che la versione MVC di System.Web contenga solo un sottoinsieme di quello spazio dei nomi?
John Peters,

2
@ user1522548 (dovresti creare un account) Assembly System.Web.dll, v4.0.0.0 ha sicuramente HTTPContext che ho appena controllato il mio codice sorgente.
PUG

Errore mio, ho un riferimento nel file a System.Web.MVC e NOT System.Web. Grazie per l'aiuto.
John Peters,

7

So che questo è un argomento più vecchio, tuttavia deridere un'applicazione MVC per i test unitari è qualcosa che facciamo regolarmente.

Volevo solo aggiungere le mie esperienze deridendo un'applicazione MVC 3 usando Moq 4 dopo l'aggiornamento a Visual Studio 2013. Nessuno dei test unit funzionava in modalità debug e HttpContext mostrava "impossibile valutare l'espressione" quando cercava di sbirciare le variabili .

Risulta che Visual Studio 2013 ha problemi con la valutazione di alcuni oggetti. Per far funzionare di nuovo il debug delle applicazioni Web derise, ho dovuto controllare la "Modalità di compatibilità gestita" in Strumenti => Opzioni => Debug => Impostazioni generali.

In genere faccio qualcosa del genere:

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


}

E iniziando il contesto in questo modo

FakeHttpContext.SetFakeContext(moController);

E chiamando direttamente il metodo nel controller

long lReportStatusID = -1;
var result = moController.CancelReport(lReportStatusID);

C'è una buona ragione per impostarla in questo modo rispetto alla risposta accettata? Questo sembra più complicato e non sembra offrire alcun vantaggio extra.
Kilkfoe,

1
Offre un metodo di derisione più dettagliato / modulare
Vincent Buscarello,

4

Se la tua applicazione di terze parti reindirizza internamente, quindi è meglio deridere HttpContext nel modo seguente:

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" } };
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.