Test jednostkowy HttpContext.Aktualne.Cache czy inne metody po stronie serwera w C#?

Podczas tworzenia testu jednostkowego dla klasy, która używa HttpContext.Current.Cache class , dostaję błąd podczas używania NUnit. Funkcjonalność jest podstawowa - sprawdź, czy element znajduje się w pamięci podręcznej, a jeśli nie, utwórz go i włóż do:

if (HttpContext.Current.Cache["Some_Key"] == null) {
    myObject = new Object();
    HttpContext.Current.Cache.Insert("Some_Key", myObject);
}
else {
    myObject = HttpContext.Current.Cache.Get("Some_Key");
}

Wywołanie tego z testu jednostkowego kończy się niepowodzeniem z at NullReferenceException przy napotkaniu pierwszej linii Cache. W Javie użyłbym Cactus do testowania kodu po stronie serwera. Czy istnieje podobne narzędzie, którego mogę użyć do kodu C#? to pytanie wspomina mock Framework - czy tylko w ten sposób mogę przetestować te metody? Czy istnieje podobne narzędzie do uruchamiania testów dla C#?

Nie sprawdzam również, czy Cache jest null, ponieważ nie chcę pisać kodu specjalnie dla testu jednostkowego i zakładam, że będzie on zawsze ważny podczas uruchamiania na serwerze. Czy jest to poprawne, czy powinienem dodać null checks wokół pamięci podręcznej?

Author: Community, 2009-01-28

14 answers

Sposobem na to jest uniknięcie bezpośredniego użycia HttpContext lub innych podobnych klas i zastąpienie ich mockami. W końcu nie próbujesz testować, czy HttpContext działa poprawnie( to zadanie Microsoftu), po prostu próbujesz sprawdzić, czy metody zostały wywołane, kiedy powinny.

Kroki (na wypadek, gdybyś chciał poznać technikę bez przekopywania się przez masę blogów):

  1. Stwórz interfejs, który opisuje metody, których chcesz użyć w Twoje buforowanie (prawdopodobnie rzeczy takie jak GetItem, SetItem, ExpireItem). Nazwij to ICache lub jak chcesz

  2. Tworzy klasę, która implementuje ten interfejs i przekazuje metody do rzeczywistego HttpContext

  3. Utwórz klasę, która implementuje ten sam interfejs i działa jak makieta pamięci podręcznej. Może użyć słownika lub czegoś takiego, jeśli zależy ci na zapisywaniu obiektów

  4. Zmień oryginalny kod, aby w ogóle nie używał HttpContext, a zamiast tego używa tylko ICache. Następnie kod będzie musiał uzyskać instancję ICache - możesz albo przekazać instancję w konstruktorze klas (to wszystko, czym naprawdę jest iniekcja zależności), albo umieścić ją w jakiejś zmiennej globalnej.

  5. W aplikacji produkcyjnej Ustaw ICache jako rzeczywistą pamięć podręczną wspieraną przez HttpContext, a w testach jednostkowych Ustaw ICache jako makietę pamięci podręcznej.

  6. Zysk!

 42
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 05:37:00

Zgadzam się z innymi, że użycie interfejsu byłoby najlepszą opcją, ale czasami po prostu nie jest możliwe, aby zmienić istniejący system wokół. Oto kod, który właśnie zmiksowałem z jednego z moich projektów, który powinien dać Ci wyniki, których szukasz. To najdalsza rzecz od ładnego lub świetnego rozwiązania, ale jeśli naprawdę nie możesz zmienić kodu, powinieneś wykonać zadanie.

using System;
using System.IO;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Web;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;

[TestFixture]
public class HttpContextCreation
{
    [Test]
    public void TestCache()
    {
        var context = CreateHttpContext("index.aspx", "http://tempuri.org/index.aspx", null);
        var result = RunInstanceMethod(Thread.CurrentThread, "GetIllogicalCallContext", new object[] { });
        SetPrivateInstanceFieldValue(result, "m_HostContext", context);

        Assert.That(HttpContext.Current.Cache["val"], Is.Null);

        HttpContext.Current.Cache["val"] = "testValue";
        Assert.That(HttpContext.Current.Cache["val"], Is.EqualTo("testValue"));
    }

    private static HttpContext CreateHttpContext(string fileName, string url, string queryString)
    {
        var sb = new StringBuilder();
        var sw = new StringWriter(sb);
        var hres = new HttpResponse(sw);
        var hreq = new HttpRequest(fileName, url, queryString);
        var httpc = new HttpContext(hreq, hres);
        return httpc;
    }

    private static object RunInstanceMethod(object source, string method, object[] objParams)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        var type = source.GetType();
        var m = type.GetMethod(method, flags);
        if (m == null)
        {
            throw new ArgumentException(string.Format("There is no method '{0}' for type '{1}'.", method, type));
        }

        var objRet = m.Invoke(source, objParams);
        return objRet;
    }

    public static void SetPrivateInstanceFieldValue(object source, string memberName, object value)
    {
        var field = source.GetType().GetField(memberName, BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance);
        if (field == null)
        {
            throw new ArgumentException(string.Format("Could not find the private instance field '{0}'", memberName));
        }

        field.SetValue(source, value);
    }
}
 29
Author: Brian Surowiec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-05 08:06:03
HttpContext.Current = new HttpContext(new HttpRequest(null, "http://tempuri.org", null), new HttpResponse(null));
 17
Author: Shawn Miller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-07-23 02:06:07

Jeśli używasz. NET 3.5, możesz użyć System.Www.Abstrakcje w aplikacji.

Justin Etheredge ma świetny post o tym, jak makować HttpContext (który zawiera klasę cache).

Z przykładu Justina, przekazuję HttpContextBase do moich kontrolerów za pomocą HttpContextFactory.GetHttpContext. Podczas wyśmiewania ich, po prostu buduję makietę, aby wywołać obiekt pamięci podręcznej.

 6
Author: Steve Wright,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 02:43:07

Istnieje nowsze podejście do obsługi pamięci podręcznej w testach jednostkowych.

Polecam użycie nowego MemoryCache Microsoftu.Domyślne podejście . Musisz użyć. NET Framework 4.0 lub nowszego i zawierać odniesienie do systemu.Runtime.Buforowanie.

Zobacz artykuł tutaj -- > http://msdn.microsoft.com/en-us/library/dd997357 (v=vs.100). aspx

MemoryCache.Domyślnie działa zarówno w aplikacjach internetowych, jak i innych. Więc chodzi o to, że aktualizujesz swoją aplikację webapp aby usunąć odniesienia do HttpContext.Aktualne.Pamięci podręcznej i zastąp je referencjami do MemoryCache.Default. Później, po uruchomieniu zdecyduj się przetestować te same metody, obiekt cache jest nadal dostępny i nie będzie null. (Ponieważ nie jest zależny od HttpContext.)

W ten sposób nie musisz nawet kopiować komponentu cache.

 5
Author: ClearCloud8,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-30 16:18:15

Ogólny konsensus wydaje się być taki, że prowadzenie czegokolwiek związanego z HttpContext z poziomu testu jednostkowego jest całkowitym koszmarem i należy go unikać, jeśli to możliwe.

Myślę, że jesteś na dobrej drodze jeśli chodzi o wyśmiewanie. I like RhinoMocks ( http://ayende.com/projects/rhino-mocks.aspx).

Też czytałem kilka dobrych rzeczy o MoQ ( http://code.google.com/p/moq ), chociaż jeszcze nie próbowałem.

Jeśli naprawdę chcesz napisać unit testable Web UIs w C# , sposób, w jaki ludzie wydają się być kierowane jest do korzystania z frameworku MVC (http://www.asp.net/mvc ) zamiast WebForms...

 2
Author: Antony Perkov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 02:17:24

Możesz użyć klasy HttpContextBase w systemie.Www.Abstrakcje.dll. To nowy dll w. NET 3.5.

Przykład użycia znajdziesz w poniższym linku.

Http://vkreynin.wordpress.com/2009/03/23/stub-htttpcontext/

 2
Author: Vadim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-24 13:15:53

To może na Twojej ulicy ... Phil Haack pokazuje, z pomocą Rhino mocks, jak naśmiewać httpcontext w asp mvc, ale wyobrażam sobie, że można go zastosować do formularzy internetowych.

Clicky!!

Mam nadzieję, że to pomoże.

 1
Author: WestDiscGolf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-28 12:20:55

Jeśli nie zależy ci na testowaniu pamięci podręcznej, możesz to zrobić poniżej:

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

Możesz również moq jak poniżej

var controllerContext = new Mock<ControllerContext>();
      controllerContext.SetupGet(p => p.HttpContext.Session["User"]).Returns(TestGetUser);
      controllerContext.SetupGet(p => p.HttpContext.Request.Url).Returns(new Uri("http://web1.ml.loc"));
 1
Author: PUG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-04-16 00:38:53

Wszystkie te pytania programistyczne wymagają modelu programowania opartego na interfejsie, w którym zaimplementujesz interfejs dwukrotnie. Jeden dla prawdziwego kodu i jeden dla makiety.

Instancjacja jest kolejnym problemem. Istnieje kilka wzorców projektowych, które można wykorzystać do tego celu. Zobacz na przykład słynne wzorce kreacji GangOfFour (GOF ) lub wzorce iniekcji zależności.

ASP.Net MVC w rzeczywistości korzysta z tego podejścia opartego na interfejsie i dlatego jest znacznie bardziej nadaje się do testów jednostkowych.

 0
Author: Rine le Comte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-29 08:21:41

Jak wszyscy tutaj mówili, jest problem z HTTPContext, obecnie Typemock jest jedynym frameworkiem, który może udawać bezpośrednio bez żadnych owijek lub abstrakcji.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-28 12:28:10

Obiekt pamięci podręcznej jest trudny do sfałszowania, ponieważ jest to zapieczętowany obszar. NET framework. Zazwyczaj obejdę to, budując klasę wrappera pamięci podręcznej, która akceptuje obiekt Menedżera pamięci podręcznej. Do testowania używam Menedżera pamięci podręcznej; do produkcji używam Menedżera pamięci podręcznej, który faktycznie uzyskuje dostęp do HttpRuntime.Cache.

W zasadzie sam usuwam pamięć podręczną.

 0
Author: Chris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-28 12:46:15

Przykład dla osób korzystających z MVC 3 i MOQ:

Moja metoda kontrolera ma następującą linię:

model.Initialize(HttpContext.Cache[Constants.C_CustomerTitleList] 
as Dictionary<int, string>);

Jako taki, każdy test jednostkowy zawiedzie, Ponieważ Nie konfiguruję HttpContext.Cache.

W moim teście jednostkowym układam następująco:

 HttpRuntime.Cache[Constants.C_CustomerTitleList] = new Dictionary<int, string>();

 var mockRequest = new Mock<HttpRequestBase>();
 mockRequest.SetupGet(m => m.Url).Returns(new Uri("http://localhost"));

 var context = new Mock<HttpContextBase>(MockBehavior.Strict);
 context.SetupGet(x => x.Request).Returns(mockRequest.Object);
 context.SetupGet(x => x.Cache).Returns(HttpRuntime.Cache);

 var controllerContext = new Mock<ControllerContext>();
 controllerContext.SetupGet(x => x.HttpContext).Returns(context.Object);

 customerController.ControllerContext = controllerContext.Object;
 0
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-26 11:41:33

Może spróbować...

 Isolate.WhenCalled(() => HttpContext.Current).ReturnRecursiveFake();
 var fakeSession = HttpContext.Current.Session;
 Isolate.WhenCalled(() => fakeSession.SessionID).WillReturn("1");
 0
Author: Doug Thompson - DouggyFresh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-12 15:57:13