2014-09-30 8 views
5

मैं एमवीसी 5 वेब अनुप्रयोग के लिए यूनिट टेस्ट लिख रहा हूं। मैंने अपने परीक्षण से HttpContext.Current का मज़ाक उड़ाया है। जब कोड के रूप परीक्षण httpSessionStateAfter फेंकHttpContext.Current प्रतीक्षा के बाद शून्य है (केवल यूनिट परीक्षणों में)

System.AggregateException निम्नलिखित चलाने: एक या अधिक त्रुटियां हुईं।
----> System.NullReferenceException: ऑब्जेक्ट संदर्भ किसी ऑब्जेक्ट के उदाहरण पर सेट नहीं है।

यह तब होता है जब मैं यूनिट परीक्षण चलाता हूं। जब एप्लिकेशन इस काम को ठीक करता है। मैं reshaper परीक्षण धावक के साथ नुनिट 2.6.3 का उपयोग कर रहा हूँ।

var httpSessionStateBefour = System.Web.HttpContext.Current.Session; 
var Person= await Db.Persons.FirstOrDefaultAsync(); 
var httpSessionStateAfter = System.Web.HttpContext.Current.Session; 

इस समस्या को कैसे खत्म करें?

यह मैं कैसे HttpContext

HttpContext.Current = Fakes.FakeHttpContext();    
HttpContext.Current.Session.Add("IsUserSiteAdmin", true); 
HttpContext.Current.Session.Add("CurrentSite", null); 

public static class Fakes 
{ 
    public static HttpContext FakeHttpContext() 
    { 
     var httpRequest = new HttpRequest("", "http://stackoverflow/", ""); 
     var stringWriter = new StringWriter(); 
     var httpResponce = new HttpResponse(stringWriter); 
     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}); 

     return httpContext; 
    } 
} 
+1

आपका प्रश्न वास्तव में क्या है? –

+0

@ ट्रेगेडियन मैं अनुमान लगा रहा हूं "httpSessionStateAfter' शून्य क्यों है लेकिन' httpSessionStateBefour' नहीं है? " – DavidG

+2

आप किस इकाई परीक्षण ढांचे का उपयोग कर रहे हैं? क्या आप वाकई निष्पादन संदर्भों का समर्थन करते हैं? उदाहरण के लिए एनयूनीट के पुराने संस्करण 'प्रतीक्षा' के बारे में कुछ भी नहीं जानते हैं, इसलिए वे निरंतरता को कॉन्फ़िगर नहीं कर सकते हैं। ASP.NET अनुप्रयोगों में निरंतरता * * अलग * थ्रेडपूल थ्रेड पर निष्पादित की जाती है जो मूल निष्पादन संदर्भ –

उत्तर

8

HttpContext.Current साथ काम करने के एक बहुत भयानक संपत्ति माना जाता है नकली है, यह अपने एएसपी.नेट घर से बाहर व्यवहार नहीं करता है। अपने कोड को ठीक करने का सबसे अच्छा तरीका इस संपत्ति को देखना बंद करना और आपके द्वारा परीक्षण किए जा रहे कोड से अलग करने का तरीका ढूंढना है। उदाहरण के लिए, आप एक इंटरफ़ेस बना सकते हैं जो आपके वर्तमान सत्र के डेटा का प्रतिनिधित्व करता है और आपके द्वारा परीक्षण किए जा रहे घटक को उस इंटरफ़ेस का खुलासा करता है, जिसके कार्यान्वयन के लिए HTTP संदर्भ की आवश्यकता होती है।


रूट समस्या यह है कि HttpContext.Current कैसे काम करता है। यह संपत्ति एएसपी.नेट ढांचे में "जादुई" है, जिसमें यह एक अनुरोध-प्रतिक्रिया संचालन के लिए अद्वितीय है, लेकिन निष्पादन के रूप में धागे के बीच कूदता है - इसे चुनिंदा रूप से धागे के बीच साझा किया जाता है।

जब आप ASP.NET प्रसंस्करण पाइपलाइन के बाहर HttpContext.Current का उपयोग करते हैं, तो जादू दूर हो जाता है। जब आप अपने जैसे थ्रेड स्विच करते हैं तो एसिंक्रोनस प्रोग्रामिंग शैली के साथ, संपत्ति जारी रखने के बाद null है।

यदि आप HttpContext.Current पर हार्ड निर्भरता को हटाने के लिए बिल्कुल अपना कोड नहीं बदल सकते हैं, तो आप अपने स्थानीय संदर्भ का लाभ उठाकर इस परीक्षा को धोखा दे सकते हैं: जब आप निरंतरता की घोषणा करते हैं तो स्थानीय क्षेत्र में सभी चर निरंतरता के संदर्भ के लिए उपलब्ध कराए जाते हैं ।

// Bring the current value into local scope. 
var context = System.Web.HttpContext.Current; 

var httpSessionStateBefore = context.Session; 
var person = await Db.Persons.FirstOrDefaultAsync(); 
var httpSessionStateAfter = context.Session; 

स्पष्ट है कि, इस केवल अपने वर्तमान परिदृश्य के लिए काम करते हैं। यदि आप किसी अन्य दायरे में await पेश करते हैं, तो कोड अचानक टूट जाएगा; यह तेज़ और गंदा जवाब है कि मैं आपको अधिक मजबूत समाधान को अनदेखा करने और आगे बढ़ाने के लिए प्रोत्साहित करता हूं।

+0

पहली बात यह है कि मैं एकीकरण परीक्षण कर रहा हूं। मैं नियंत्रक के लिए परीक्षण लिखता हूं। यह डेटाबेस तक नीचे जायेगा। मैंने DbContext के अंदर सत्र से विवरण प्राप्त करने के लिए HttpContext.Current का उपयोग किया। इससे पहले मैं इसे SaveChanges() विधि के अंदर करता हूं जो कुछ async कॉल के बाद निष्पादित करता है। इसके बजाय अब मैं इसे एक कन्स्ट्रक्टर प्रारंभिक संपत्ति के रूप में जोड़ता हूं जो एसिंक कॉल से पहले निष्पादित होता है। –

9

सबसे पहले, मैं अनुशंसा करता हूं कि आप HttpContext.Current से जितना संभव हो सके अपना कोड अलग करें; न केवल यह आपके कोड को अधिक टेस्टेबल बना देगा, लेकिन यह आपको एएसपी.NET vNext के लिए तैयार करने में मदद करेगा, जो अधिक ओविन-जैसा है (HttpContext.Current के बिना)।

हालांकि, इसमें कई बदलावों की आवश्यकता हो सकती है, जिन्हें आप अभी तक तैयार नहीं कर सकते हैं। HttpContext.Current को ठीक से नकल करने के लिए, आपको यह समझने की आवश्यकता है कि यह कैसे काम करता है।

HttpContext.Current एक प्रति-थ्रेड वैरिएबल है जो एएसपी.NET SynchronizationContext द्वारा नियंत्रित होता है।यह SynchronizationContext एक "अनुरोध संदर्भ" है, जो वर्तमान अनुरोध का प्रतिनिधित्व करता है; जब कोई नया अनुरोध आता है तो यह एएसपी.NET द्वारा बनाया गया है। यदि आपके पास अधिक जानकारी है तो मेरे पास MSDN article on SynchronizationContext है।

के रूप में मैं अपने async intro blog post, जब आप await एक Task, डिफ़ॉल्ट रूप से यह वर्तमान "संदर्भ" पर कब्जा और का उपयोग करें कि async विधि फिर से शुरू करने में समझाने। जब async विधि एएसपी.NET अनुरोध संदर्भ में चल रही है, तो await द्वारा कैप्चर किया गया "संदर्भ" ASP.NET SynchronizationContext है। जब async विधि फिर से शुरू हो जाती है (संभवतः एक अलग थ्रेड पर), async विधि को फिर से शुरू करने से पहले ASP.NET SynchronizationContextHttpContext.Current सेट करेगा। इस प्रकार async/await एएसपी.NET होस्ट के भीतर काम करता है।

अब, जब आप एक यूनिट परीक्षण में एक ही कोड चलाते हैं, तो व्यवहार अलग होता है। विशेष रूप से, HttpContext.Current सेट करने के लिए कोई ASP.NET SynchronizationContext नहीं है। मुझे लगता है कि आपकी यूनिट टेस्ट विधि Task लौटाती है, इस स्थिति में NUnit SynchronizationContext बिल्कुल प्रदान नहीं करता है। इसलिए, जब async विधि फिर से शुरू हो जाती है (संभवतः एक अलग धागे पर), तो HttpContext.Current समान नहीं हो सकता है।

इसे ठीक करने के कुछ अलग तरीके हैं। एक विकल्प अपने SynchronizationContext को लिखना है जो HttpContext.Current को सुरक्षित करता है, जैसे एएसपी.नेट एक करता है। a SynchronizationContext that I wrote called AsyncContext का उपयोग करना एक आसान (लेकिन कम कुशल) विकल्प है, जो सुनिश्चित करता है कि async विधि उसी थ्रेड पर फिर से शुरू हो जाएगी। आपको my AsyncEx library from NuGet इंस्टॉल करने में सक्षम होना चाहिए और फिर AsyncContext.Run पर कॉल के अंदर अपनी इकाई परीक्षण विधियों को लपेटना चाहिए। ध्यान दें कि इकाई परीक्षण तरीकों अब तुल्यकालिक हैं:

[Test] 
public void MyTest() 
{ 
    AsyncContext.Run(async() => 
    { 
    // Test logic goes here: set HttpContext.Current, etc. 
    }); 
} 
+0

सहायता के लिए धन्यवाद क्या आपका 'AsyncContext' नेस्टेड' प्रतीक्षा 'के लिए भी काम करता है? मैंने अभी इसे .NET 4.5 प्रोजेक्ट में आज़माया है जहां मेरी टेस्ट विधि नकली में 'HttpContext.Current' सेट करती है और फिर उस विधि को कॉल करती है जो अंदर' प्रतीक्षा 'का उपयोग करती है।शीर्ष स्तर 'प्रतीक्षा' के लिए यह ठीक काम करता है (और यह 'AsyncContext' के बिना भी किया गया था, लेकिन एक नेस्टेड' प्रतीक्षा 'के ठीक बाद' HttpContext.Current' फिर से शून्य है। नेस्टेड 'प्रतीक्षा' व्युत्पन्न विधि 'FindByNameAsync () '' Microsoft.AspNet.Identity.UserManager' से प्राप्त कक्षा से। – JustAMartin

+0

@ जस्टमार्टिन: 'AsyncContext' केवल गारंटी देता है कि विधि उसी * थ्रेड * पर फिर से शुरू होगी। यह' HttpContext.Current' के साथ कुछ भी विशेष नहीं करता है। ऐसा लगता है कि कुछ एएसपी.NET कोड 'HttpContext.Current' को साफ़ कर सकते हैं। जब तक आपका कोड' ConfigureAwait (false) 'का उपयोग नहीं करता है, जो थ्रेड कूद का कारण बनता है। –

1

मैं जब मेरे कोड में एक मुद्दा ... जहां HTTPContext.Current एक Async MVC कार्रवाई में एक का इंतजार के बाद रिक्त है होने इस सवाल के लिए आया था। मैं इसे यहां पोस्ट करता हूं क्योंकि मेरे जैसे अन्य लोग यहां उतर सकते हैं।

मेरी सामान्य सिफारिश है कि आप किसी भी स्थानीय चर में सत्र को बंद करना चाहते हैं, चर्चा के ऊपर दूसरों की तरह, लेकिन संदर्भ रखने के बारे में चिंता न करें, और इसके बजाय केवल वास्तविक वस्तुओं को पकड़ने के बारे में चिंता करें।

public async Task<ActionResult> SomeAction(SomeModel model) 
{ 
int id = (int)HttpContext.Current.Session["Id"]; 

/* Session Exists Here */ 

var somethingElseAsyncModel = await GetSomethingElseAsync(model); 

/* Session is Null Here */ 

// Do something with id, thanks to the fact we got it when we could 
} 
+0

मैं इसकी सराहना करता हूं और ऊपर की ओर जाता हूं, लेकिन आदमी, आपकी इच्छा है प्रतीक्षा के बाद सत्र परिवर्तनीय तक पहुंचने के बारे में कुछ जानकारी। – Barry

+0

@ बैरी, आप इसे एक चर (सत्र) पर इंगित करने का प्रयास कर सकते हैं, लेकिन मैंने ऐसा करने की कोशिश नहीं की है। मुझे सुरक्षित और क्लीनर लगता है मुझे जो चाहिए वह पकड़ो, लेकिन ऐसा लगता है कि आप मोदी नहीं होंगे ज्यादातर मामलों में सत्र फेंकना। साथ ही, मैं आम तौर पर जितना संभव हो सके सत्र का उपयोग करने की कोशिश करता हूं। सत्र का ओवरयूज (ग्लोबल सोचें) जब उपयोगकर्ता दो अलग-अलग ब्राउज़र विंडो/टैब का उपयोग करना शुरू करता है तो गैर-निर्धारण व्यवहार कर सकता है। – Greg

+0

सहमत होना है, मैं पहले से आवश्यक डेटा को पकड़ने के लिए अपने मॉडल का पालन कर समाप्त हुआ। मैं विरासत कोड को परिवर्तित करने पर काम कर रहा हूं जो सत्र पर भारी निर्भर करता है, यह निश्चित रूप से कई स्तरों पर असुरक्षित है। मैं इस पर जानकारी की सराहना करता हूं, मुझे काफी मदद मिली है! – Barry

संबंधित मुद्दे