Il punto di test unitario è di isolare tutte le dipendenze. Altrimenti non sei un test unitario, sei un test di integrazione.
Il problema
Quindi come isolare le dipendenze in modo da testare solo il codice di interesse? Bene diamo un'occhiata.
Considera il seguente esempio di controller / azione:
class MyController
{
public ActionResult MyAction()
{
if (HttpContext.Current.Request.RawUrl.Contains("SomeString")
{
return Redirect("/Someurl");
}
return View();
}
}
Questo è un codice terribile per una serie di motivi (vincolo del modello, chiunque?) ... ma per la discussione attuale, mettilo da parte e osserva che HttpContext
viene creato dal metodo di azione tramite una sorta di singleton modello. Non è iniettato e non c'è modo di cambiarlo, se non quello di modificare il codice o fare qualcosa di veramente strano con l'API di profilazione .NET, come fa TypeMock. Senza sforzi eroici, sei bloccato usando il vero oggetto HttpContext nel runtime .NET.
Ora immagina di voler scrivere un test unitario per vedere se il metodo di azione esegue effettivamente un reindirizzamento quando l'URL contiene "SomeString". Come lo faresti? Dove prendi HttpContext? Ne hai bisogno di uno vero. Quindi fai girare un server web sulla tua macchina per testare le unità? Richiamalo con un client web? Dovrai iscriverti? Creare i cookie? Ugh.
Bottom line: Non hai modo di isolare la dipendenza da HttpContext.
Un modo migliore
Se dovessimo modernizzarlo con un po 'di DI, potrebbe apparire come questo:
class MyController
{
private readonly HttpContextBase _httpContext;
public MyController(HttpContextBase context) //Injected
{
_httpContext = context;
}
public ActionResult MyAction()
{
if (_httpContext.Request.RawUrl.Contains("SomeString")
{
return Redirect("/Someurl");
}
return View();
}
}
Ora possiamo testare questo, fornendo un mockup del contesto HTTP conforme all'interfaccia di HttpContextBase, come questo:
class MockHttpRequest: System.Web.HttpRequestBase
{
public override string RawUrl
{
get
{
return "SomeString";
}
}
}
class MockHttpContext : System.Web.HttpContextBase
{
public override System.Web.HttpRequestBase Request
{
get
{
return new MockHttpRequest();
}
}
}
E nel codice di test dell'unità:
//Arrange
var context = new MockHttpContext();
var controller = new MyController(context);
//Act
var result = controller.MyAction();
//Assert
Assert.IsTrue(result is RedirectResult);
Pezzo di torta. Non serve un server web. Non hai nemmeno bisogno di Moq o TypeMock o Fakes. Puoi fare tutto da solo. Non sarebbe solo più difficile, ma sarebbe impossibile nel modo tradizionale, senza una struttura beffarda.
P.S.
Per inciso, Microsoft ha pensato che fosse così prezioso che hanno aggiunto HttpContextBase
solo per poter essere iniettato e sostituito nei test di unità (il normale HttpContext
è sigillato). Se si sono presi la briga di aggiungerlo al runtime, probabilmente vale la pena utilizzarlo.