Pengaturan HttpContext.Current.Session dalam tes unit

185

Saya memiliki layanan web yang saya coba uji unit. Dalam layanan itu menarik beberapa nilai dari HttpContextseperti ini:

 m_password = (string)HttpContext.Current.Session["CustomerId"];
 m_userID = (string)HttpContext.Current.Session["CustomerUrl"];

dalam tes unit saya membuat konteks menggunakan permintaan pekerja sederhana, seperti:

SimpleWorkerRequest request = new SimpleWorkerRequest("", "", "", null, new StringWriter());
HttpContext context = new HttpContext(request);
HttpContext.Current = context;

Namun, setiap kali saya mencoba mengatur nilai HttpContext.Current.Session

HttpContext.Current.Session["CustomerId"] = "customer1";
HttpContext.Current.Session["CustomerUrl"] = "customer1Url";

Saya mendapatkan pengecualian referensi nol yang mengatakan HttpContext.Current.Sessionnol.

Apakah ada cara untuk menginisialisasi sesi saat ini dalam unit test?

DaveB
sumber
Apakah Anda mencoba metode ini ?
Raj Ranjhan
Gunakan HttpContextBase jika Anda bisa.
jrummell

Jawaban:

105

Kami harus mengejek HttpContextdengan menggunakan HttpContextManagerdan memanggil pabrik dari dalam aplikasi kami serta Tes Unit

public class HttpContextManager 
{
    private static HttpContextBase m_context;
    public static HttpContextBase Current
    {
        get
        {
            if (m_context != null)
                return m_context;

            if (HttpContext.Current == null)
                throw new InvalidOperationException("HttpContext not available");

            return new HttpContextWrapper(HttpContext.Current);
        }
    }

    public static void SetCurrentContext(HttpContextBase context)
    {
        m_context = context;
    }
}

Anda kemudian akan mengganti semua panggilan HttpContext.Currentdengan HttpContextManager.Currentdan memiliki akses ke metode yang sama. Kemudian ketika Anda menguji, Anda juga dapat mengakses HttpContextManagerdan mengejek harapan Anda

Ini adalah contoh menggunakan Moq :

private HttpContextBase GetMockedHttpContext()
{
    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>();
    var urlHelper = new Mock<UrlHelper>();

    var routes = new RouteCollection();
    MvcApplication.RegisterRoutes(routes);
    var requestContext = new Mock<RequestContext>();
    requestContext.Setup(x => x.HttpContext).Returns(context.Object);
    context.Setup(ctx => ctx.Request).Returns(request.Object);
    context.Setup(ctx => ctx.Response).Returns(response.Object);
    context.Setup(ctx => ctx.Session).Returns(session.Object);
    context.Setup(ctx => ctx.Server).Returns(server.Object);
    context.Setup(ctx => ctx.User).Returns(user.Object);
    user.Setup(ctx => ctx.Identity).Returns(identity.Object);
    identity.Setup(id => id.IsAuthenticated).Returns(true);
    identity.Setup(id => id.Name).Returns("test");
    request.Setup(req => req.Url).Returns(new Uri("http://www.google.com"));
    request.Setup(req => req.RequestContext).Returns(requestContext.Object);
    requestContext.Setup(x => x.RouteData).Returns(new RouteData());
    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

    return context.Object;
}

dan kemudian menggunakannya dalam pengujian unit Anda, saya menyebutnya dalam metode Test Init saya

HttpContextManager.SetCurrentContext(GetMockedHttpContext());

Anda kemudian dapat, dalam metode di atas menambahkan hasil yang diharapkan dari Sesi yang Anda harapkan tersedia untuk layanan web Anda.

Anthony Shaw
sumber
1
tetapi ini tidak menggunakan SimpleWorkerRequest
knocte
dia mencoba untuk mengejek HttpContext sehingga SimpleWorkerRequest-nya akan memiliki akses ke nilai-nilai dari HttpContext, dia akan menggunakan HttpContextFactory dalam layanannya
Anthony Shaw
Apakah disengaja bahwa bidang dukungan m_context hanya dikembalikan untuk konteks tiruan (ketika diatur melalui SetCurrentContext) dan bahwa untuk HttpContext nyata, pembungkus dibuat untuk setiap panggilan ke saat ini?
Harga Stephen
Ya itu. m_context adalah tipe HttpContextBase dan mengembalikan HttpContextWrapper mengembalikan HttpContextBase dengan Current HttpContext
Anthony Shaw
1
HttpContextManagerakan menjadi nama yang lebih baik daripada HttpContextSourcetetapi saya setuju HttpContextFactorymenyesatkan.
Profesor pemrograman
298

Anda dapat "memalsukannya" dengan membuat yang baru HttpContextseperti ini:

http://www.necronet.org/archive/2010/07/28/unit-testing-code-that-uses-httpcontext-current-session.aspx

Saya telah mengambil kode itu dan menaruhnya di kelas pembantu statis seperti:

public static HttpContext FakeHttpContext()
{
    var httpRequest = new HttpRequest("", "http://example.com/", "");
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    var sessionContainer = new HttpSessionStateContainer("id", new SessionStateItemCollection(),
                                            new HttpStaticObjectsCollection(), 10, true,
                                            HttpCookieMode.AutoDetect,
                                            SessionStateMode.InProc, false);

    httpContext.Items["AspSession"] = typeof(HttpSessionState).GetConstructor(
                                BindingFlags.NonPublic | BindingFlags.Instance,
                                null, CallingConventions.Standard,
                                new[] { typeof(HttpSessionStateContainer) },
                                null)
                        .Invoke(new object[] { sessionContainer });

    return httpContext;
}

Atau alih-alih menggunakan refleksi untuk membuat HttpSessionStateinstance baru , Anda bisa menempelkan Anda HttpSessionStateContainerke HttpContext(sesuai komentar Brent M. Spell):

SessionStateUtility.AddHttpSessionStateToContext(httpContext, sessionContainer);

dan kemudian Anda bisa menyebutnya di unit test Anda seperti:

HttpContext.Current = MockHelper.FakeHttpContext();
Milox
sumber
24
Saya suka jawaban ini lebih baik daripada yang diterima karena mengubah kode produksi Anda untuk mendukung kegiatan pengujian Anda adalah praktik yang buruk. Memang, kode produksi Anda harus mengabstraksi ruang nama pihak ketiga seperti ini, tetapi ketika Anda bekerja dengan kode lawas, Anda tidak selalu memiliki kontrol ini atau kemewahan sebagai faktor ulang.
Sean Glover
29
Anda tidak harus menggunakan refleksi untuk membangun contoh HttpSessionState baru. Anda bisa melampirkan HttpSessionStateContainer Anda ke HttpContext menggunakan SessionStateUtility.AddHttpSessionStateToContext.
Brent M. Spell
MockHelper hanyalah nama kelas di mana metode statisnya, Anda dapat menggunakan nama apa pun yang Anda inginkan.
Milox
Saya sudah mencoba menerapkan jawaban Anda tetapi Sesi ini masih nol. Tolong, lihat di Posting saya stackoverflow.com/questions/23586765/… . Terima kasih
Joe
Server.MapPath()tidak akan berfungsi jika Anda menggunakan ini juga.
Yuck
45

Solusi Milox lebih baik daripada yang diterima IMHO tapi saya punya beberapa masalah dengan implementasi ini ketika menangani url dengan querystring .

Saya membuat beberapa perubahan agar berfungsi dengan baik dengan url apa pun dan untuk menghindari Refleksi.

public static HttpContext FakeHttpContext(string url)
{
    var uri = new Uri(url);
    var httpRequest = new HttpRequest(string.Empty, uri.ToString(),
                                        uri.Query.TrimStart('?'));
    var stringWriter = new StringWriter();
    var httpResponse = new HttpResponse(stringWriter);
    var httpContext = new HttpContext(httpRequest, httpResponse);

    var sessionContainer = new HttpSessionStateContainer("id",
                                    new SessionStateItemCollection(),
                                    new HttpStaticObjectsCollection(),
                                    10, true, HttpCookieMode.AutoDetect,
                                    SessionStateMode.InProc, false);

    SessionStateUtility.AddHttpSessionStateToContext(
                                         httpContext, sessionContainer);

    return httpContext;
}
giammin
sumber
Ini memungkinkan Anda untuk berpura-pura httpContext.Session, ada ide bagaimana melakukan hal yang sama httpContext.Application?
KyleMit
39

Saya mengalami sesuatu tentang ini beberapa waktu lalu.

Unit Pengujian HttpContext.Current.Session di MVC3 .NET

Semoga ini bisa membantu.

[TestInitialize]
public void TestSetup()
{
    // We need to setup the Current HTTP Context as follows:            

    // Step 1: Setup the HTTP Request
    var httpRequest = new HttpRequest("", "http://localhost/", "");

    // Step 2: Setup the HTTP Response
    var httpResponce = new HttpResponse(new StringWriter());

    // Step 3: Setup the Http Context
    var httpContext = new HttpContext(httpRequest, httpResponce);
    var sessionContainer = 
        new HttpSessionStateContainer("id", 
                                       new SessionStateItemCollection(),
                                       new HttpStaticObjectsCollection(), 
                                       10, 
                                       true,
                                       HttpCookieMode.AutoDetect,
                                       SessionStateMode.InProc, 
                                       false);
    httpContext.Items["AspSession"] = 
        typeof(HttpSessionState)
        .GetConstructor(
                            BindingFlags.NonPublic | BindingFlags.Instance,
                            null, 
                            CallingConventions.Standard,
                            new[] { typeof(HttpSessionStateContainer) },
                            null)
        .Invoke(new object[] { sessionContainer });

    // Step 4: Assign the Context
    HttpContext.Current = httpContext;
}

[TestMethod]
public void BasicTest_Push_Item_Into_Session()
{
    // Arrange
    var itemValue = "RandomItemValue";
    var itemKey = "RandomItemKey";

    // Act
    HttpContext.Current.Session.Add(itemKey, itemValue);

    // Assert
    Assert.AreEqual(HttpContext.Current.Session[itemKey], itemValue);
}
Ro Hit
sumber
Bekerja sangat baik dan sederhana ... Terima kasih!
mggSoft
12

Jika Anda menggunakan kerangka kerja MVC, ini harusnya berfungsi. Saya menggunakan Milox's FakeHttpContext dan menambahkan beberapa baris kode tambahan. Idenya datang dari pos ini:

http://codepaste.net/p269t8

Ini sepertinya berfungsi di MVC 5. Saya belum mencoba ini di versi MVC sebelumnya.

HttpContext.Current = MockHttpContext.FakeHttpContext();

var wrapper = new HttpContextWrapper(HttpContext.Current);

MyController controller = new MyController();
controller.ControllerContext = new ControllerContext(wrapper, new RouteData(), controller);

string result = controller.MyMethod();
Nimblejoe
sumber
3
Tautan rusak sehingga mungkin menempatkan kode di sini waktu berikutnya.
Rhyous
11

Anda dapat mencoba FakeHttpContext :

using (new FakeHttpContext())
{
   HttpContext.Current.Session["CustomerId"] = "customer1";       
}
vad
sumber
Bekerja dengan baik dan sangat mudah digunakan
Beanwah
8

Di asp.net Core / MVC 6 rc2 Anda dapat mengatur HttpContext

var SomeController controller = new SomeController();

controller.ControllerContext = new ControllerContext();
controller.ControllerContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

rc 1 tadinya

var SomeController controller = new SomeController();

controller.ActionContext = new ActionContext();
controller.ActionContext.HttpContext = new DefaultHttpContext();
controller.HttpContext.Session = new DummySession();

https://stackoverflow.com/a/34022964/516748

Pertimbangkan untuk menggunakan Moq

new Mock<ISession>();
KCD
sumber
7

Jawaban yang bekerja dengan saya adalah apa yang telah ditulis @Anthony, tetapi Anda harus menambahkan baris lain yang mana

    request.SetupGet(req => req.Headers).Returns(new NameValueCollection());

jadi kamu bisa menggunakan ini:

HttpContextFactory.Current.Request.Headers.Add(key, value);
yzicus
sumber
2

Coba ini:

        // MockHttpSession Setup
        var session = new MockHttpSession();

        // MockHttpRequest Setup - mock AJAX request
        var httpRequest = new Mock<HttpRequestBase>();

        // Setup this part of the HTTP request for AJAX calls
        httpRequest.Setup(req => req["X-Requested-With"]).Returns("XMLHttpRequest");

        // MockHttpContextBase Setup - mock request, cache, and session
        var httpContext = new Mock<HttpContextBase>();
        httpContext.Setup(ctx => ctx.Request).Returns(httpRequest.Object);
        httpContext.Setup(ctx => ctx.Cache).Returns(HttpRuntime.Cache);
        httpContext.Setup(ctx => ctx.Session).Returns(session);

        // MockHttpContext for cache
        var contextRequest = new HttpRequest("", "http://localhost/", "");
        var contextResponse = new HttpResponse(new StringWriter());
        HttpContext.Current = new HttpContext(contextRequest, contextResponse);

        // MockControllerContext Setup
        var context = new Mock<ControllerContext>();
        context.Setup(ctx => ctx.HttpContext).Returns(httpContext.Object);

        //TODO: Create new controller here
        //      Set controller's ControllerContext to context.Object

Dan Tambahkan kelas:

public class MockHttpSession : HttpSessionStateBase
{
    Dictionary<string, object> _sessionDictionary = new Dictionary<string, object>();
    public override object this[string name]
    {
        get
        {
            return _sessionDictionary.ContainsKey(name) ? _sessionDictionary[name] : null;
        }
        set
        {
            _sessionDictionary[name] = value;
        }
    }

    public override void Abandon()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach (var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }

    public override void Clear()
    {
        var keys = new List<string>();

        foreach (var kvp in _sessionDictionary)
        {
            keys.Add(kvp.Key);
        }

        foreach(var key in keys)
        {
            _sessionDictionary.Remove(key);
        }
    }
}

Ini akan memungkinkan Anda untuk menguji dengan sesi dan cache.

Isaac Alvarado
sumber
1

Saya mencari sesuatu yang kurang invasif daripada opsi yang disebutkan di atas. Pada akhirnya saya datang dengan solusi murahan, tetapi mungkin membuat beberapa orang bergerak sedikit lebih cepat.

Pertama saya membuat kelas TestSession :

class TestSession : ISession
{

    public TestSession()
    {
        Values = new Dictionary<string, byte[]>();
    }

    public string Id
    {
        get
        {
            return "session_id";
        }
    }

    public bool IsAvailable
    {
        get
        {
            return true;
        }
    }

    public IEnumerable<string> Keys
    {
        get { return Values.Keys; }
    }

    public Dictionary<string, byte[]> Values { get; set; }

    public void Clear()
    {
        Values.Clear();
    }

    public Task CommitAsync()
    {
        throw new NotImplementedException();
    }

    public Task LoadAsync()
    {
        throw new NotImplementedException();
    }

    public void Remove(string key)
    {
        Values.Remove(key);
    }

    public void Set(string key, byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            Remove(key);
        }
        Values.Add(key, value);
    }

    public bool TryGetValue(string key, out byte[] value)
    {
        if (Values.ContainsKey(key))
        {
            value = Values[key];
            return true;
        }
        value = new byte[0];
        return false;
    }
}

Kemudian saya menambahkan parameter opsional ke konstruktor pengendali saya. Jika parameter ada, gunakan untuk manipulasi sesi. Jika tidak, gunakan HttpContext.Session:

class MyController
{

    private readonly ISession _session;

    public MyController(ISession session = null)
    {
        _session = session;
    }


    public IActionResult Action1()
    {
        Session().SetString("Key", "Value");
        View();
    }

    public IActionResult Action2()
    {
        ViewBag.Key = Session().GetString("Key");
        View();
    }

    private ISession Session()
    {
        return _session ?? HttpContext.Session;
    }
}

Sekarang saya bisa menyuntikkan TestSession saya ke controller:

class MyControllerTest
{

    private readonly MyController _controller;

    public MyControllerTest()
    {
        var testSession = new TestSession();
        var _controller = new MyController(testSession);
    }
}
Chris Hanson
sumber
Saya sangat suka solusi Anda. KISS => Keep It Simple and Stupid ;-)
CodeNotFound
1

Tidak pernah mengejek .. tidak pernah! Solusinya cukup sederhana. Mengapa memalsukan ciptaan yang begitu indah HttpContext?

Dorong sesi ke bawah! (Hanya baris ini sudah cukup bagi kebanyakan dari kita untuk mengerti tetapi dijelaskan secara rinci di bawah)

(string)HttpContext.Current.Session["CustomerId"];adalah bagaimana kita mengaksesnya sekarang. Ubah ini menjadi

_customObject.SessionProperty("CustomerId")

Saat dipanggil dari pengujian, _customObject menggunakan toko alternatif (nilai DB atau kunci cloud [ http://www.kvstore.io/] )

Tetapi ketika dipanggil dari aplikasi nyata, _customObjectmenggunakan Session.

bagaimana ini dilakukan? yah ... Injeksi Ketergantungan!

Jadi tes dapat mengatur sesi (bawah tanah) dan kemudian memanggil metode aplikasi seolah-olah tidak tahu apa-apa tentang sesi. Kemudian uji diam-diam memeriksa apakah kode aplikasi dengan benar memperbarui sesi. Atau jika aplikasi berperilaku berdasarkan nilai sesi yang ditetapkan oleh tes.

Sebenarnya, kami akhirnya mengejek meskipun aku berkata: "tidak pernah mengejek". Karena kami tidak bisa membantu tetapi tergelincir ke aturan berikutnya, "mengejek di tempat yang paling menyakitkan!". Mengejek besar HttpContextatau mengejek sesi kecil, yang paling tidak menyakitkan? jangan tanya saya dari mana aturan ini berasal. Mari kita katakan akal sehat saja. Ini adalah bacaan yang menarik tentang tidak mengejek karena unit test dapat membunuh kita

Awan Biru
sumber
0

Jawaban @Ro Hit memberi banyak membantu saya, tetapi saya kehilangan kredensial pengguna karena saya harus memalsukan pengguna untuk pengujian unit otentikasi. Karena itu, izinkan saya menjelaskan bagaimana saya menyelesaikannya.

Menurut ini , jika Anda menambahkan metode

    // using System.Security.Principal;
    GenericPrincipal FakeUser(string userName)
    {
        var fakeIdentity = new GenericIdentity(userName);
        var principal = new GenericPrincipal(fakeIdentity, null);
        return principal;
    }

dan kemudian tambahkan

    HttpContext.Current.User = FakeUser("myDomain\\myUser");

ke baris terakhir dari TestSetupmetode yang Anda lakukan, kredensial pengguna ditambahkan dan siap digunakan untuk pengujian otentikasi.

Saya juga memperhatikan bahwa ada bagian lain dalam HttpContext yang mungkin Anda butuhkan, seperti .MapPath()metode. Ada FakeHttpContext yang tersedia, yang dijelaskan di sini dan dapat diinstal melalui NuGet.

Mat
sumber
0

Coba dengan cara ini ..

public static HttpContext getCurrentSession()
  {
        HttpContext.Current = new HttpContext(new HttpRequest("", ConfigurationManager.AppSettings["UnitTestSessionURL"], ""), new HttpResponse(new System.IO.StringWriter()));
        System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
        HttpContext.Current, new HttpSessionStateContainer("", new SessionStateItemCollection(), new HttpStaticObjectsCollection(), 20000, true,
        HttpCookieMode.UseCookies, SessionStateMode.InProc, false));
        return HttpContext.Current;
  }
Ranjan Singh
sumber