Moq ढांचे का उपयोग करके ModelState.IsValid को कैसे मॉक करें?


90

मैं ModelState.IsValidअपने कंट्रोलर एक्शन मेथड की जाँच कर रहा हूँ जो एक कर्मचारी को इस तरह बनाता है:

[HttpPost]
public virtual ActionResult Create(EmployeeForm employeeForm)
{
    if (this.ModelState.IsValid)
    {
        IEmployee employee = this._uiFactoryInstance.Map(employeeForm);
        employee.Save();
    }

    // Etc.
}

मैं Moq फ्रेमवर्क का उपयोग करके अपनी इकाई परीक्षण विधि में इसका मजाक बनाना चाहता हूं। मैंने इसे इस तरह से मज़ाक करने की कोशिश की:

var modelState = new Mock<ModelStateDictionary>();
modelState.Setup(m => m.IsValid).Returns(true);

लेकिन यह मेरी इकाई परीक्षण मामले में एक अपवाद है। क्या कोई यहाँ मेरी मदद कर सकता है?

जवाबों:


142

आपको इसका मजाक उड़ाने की जरूरत नहीं है। यदि आपके पास पहले से ही एक नियंत्रक है तो आप अपने परीक्षण को शुरू करते समय एक मॉडल राज्य त्रुटि जोड़ सकते हैं:

// arrange
_controllerUnderTest.ModelState.AddModelError("key", "error message");

// act
// Now call the controller action and it will 
// enter the (!ModelState.IsValid) condition
var actual = _controllerUnderTest.Index();

हम कैसे सेट करें ModelState.IsValid सही मामले को हिट करने के लिए? ModelState के पास एक सेटर नहीं है, और इसलिए हम निम्न कार्य नहीं कर सकते हैं: _controllerUnderTest.ModelState.IsValid = true। उसके बिना, यह कर्मचारी को नहीं
करण

4
@ न्यूटन, यह डिफ़ॉल्ट रूप से सही है। आपको सच्चे मामले को हिट करने के लिए कुछ भी निर्दिष्ट करने की आवश्यकता नहीं है। यदि आप झूठे मामले को हिट करना चाहते हैं तो आप बस एक मॉडलस्टेट त्रुटि जोड़ें जैसा कि मेरे उत्तर में दिखाया गया है।
डारिन दिमित्रोव

IMHO बेहतर समाधान पीवीसी कन्वेयर का उपयोग करना है। इस तरह से आपको अपने नियंत्रक का अधिक यथार्थवादी व्यवहार प्राप्त होता है, आपको नियति-विशेषता मान्यताओं को मॉडल सत्यापन प्रदान करना चाहिए। नीचे पोस्ट यह वर्णन कर रहा है ( stackoverflow.com/a/5580363/572612 )
व्लादिमीर श्मिट

13

एकमात्र मुद्दा जो मेरे पास ऊपर के समाधान के साथ है, वह यह है कि यदि मैं विशेषताएँ सेट करता हूं तो यह वास्तव में मॉडल का परीक्षण नहीं करता है। मैंने अपने कंट्रोलर को इस तरह सेटअप किया।

private HomeController GenerateController(object model)
    {
        HomeController controller = new HomeController()
        {
            RoleService = new MockRoleService(),
            MembershipService = new MockMembershipService()
        };
        MvcMockHelpers.SetFakeAuthenticatedControllerContext(controller);

        // bind errors modelstate to the controller
        var modelBinder = new ModelBindingContext()
        {
            ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, model.GetType()),
            ValueProvider = new NameValueCollectionValueProvider(new NameValueCollection(), CultureInfo.InvariantCulture)
        };
        var binder = new DefaultModelBinder().BindModel(new ControllerContext(), modelBinder);
        controller.ModelState.Clear();
        controller.ModelState.Merge(modelBinder.ModelState);
        return controller;
    }

ModelBinder ऑब्जेक्ट वह ऑब्जेक्ट है जो मॉडल की वैधता का परीक्षण करता है। इस तरह मैं सिर्फ ऑब्जेक्ट के मान सेट कर सकता हूं और उसका परीक्षण कर सकता हूं।


1
बहुत अच्छा, यह वही है जिसकी मुझे तलाश थी। मैं नहीं जानता कि कितने लोग इस तरह के एक पुराने प्रश्न पर पोस्ट करते हैं, लेकिन मेरे लिए यह आपके लिए महत्वपूर्ण था। धन्यवाद।
डब्ल्यू। जैक्सन

2016 में अभी भी एक महान समाधान की तरह लगता है :)
मैट

2
क्या इस तरह से कुछ के साथ अलगाव में मॉडल का परीक्षण करना बेहतर नहीं है? stackoverflow.com/a/4331964/3198973
रबरडुक

2
जबकि यह एक चतुर समाधान है, मैं @ रुबरडैक से सहमत हूं। इसके लिए एक वास्तविक, पृथक इकाई-परीक्षण होना चाहिए, मॉडल की मान्यता का अपना परीक्षण होना चाहिए, जबकि नियंत्रक के परीक्षण का अपना परीक्षण होना चाहिए। यदि मॉडल ModelBinder सत्यापन का उल्लंघन करने के लिए बदलता है, तो आपका नियंत्रक परीक्षण विफल हो जाएगा, जो कि एक गलत सकारात्मक है क्योंकि नियंत्रक तर्क नहीं तोड़ा गया है। एक अमान्य मॉडलस्टैटेबल्ड का परीक्षण करने के लिए, बस ModelState.IsValid जाँच के लिए एक नकली ModelState त्रुटि जोड़ें।
xDaevax

2

uadrive के जवाब ने मुझे रास्ते का हिस्सा बना दिया, लेकिन अभी भी कुछ अंतराल थे। इनपुट में किसी भी डेटा के बिना new NameValueCollectionValueProvider(), मॉडल बाइंडर नियंत्रक को एक खाली मॉडल से बांध देगा, न कि modelऑब्जेक्ट को।

यह ठीक है - बस अपने मॉडल को एक के रूप में क्रमबद्ध करें NameValueCollection, और फिर उसे NameValueCollectionValueProviderकंस्ट्रक्टर में पास करें । खैर, काफी नहीं। दुर्भाग्य से, यह मेरे मामले में काम नहीं किया क्योंकि मेरे मॉडल में एक संग्रह है, और NameValueCollectionValueProviderसंग्रह के साथ अच्छी तरह से नहीं खेलता है।

JsonValueProviderFactory, हालांकि बचाव यहाँ की बात आती है। इसका उपयोग तब तक किया जा सकता है DefaultModelBinderजब तक आप एक सामग्री प्रकार "application/json" निर्दिष्ट करते हैं और अपने अनुरोध के इनपुट स्ट्रीम में अपनी क्रमबद्ध JSON ऑब्जेक्ट को पास करते हैं (कृपया ध्यान दें, क्योंकि यह इनपुट स्ट्रीम एक मेमोरी स्ट्रीम है, यह एक स्मृति के रूप में, इसे अपरिहार्य रूप से छोड़ने के लिए ठीक है" धारा किसी भी बाहरी संसाधनों पर पकड़ नहीं रखती है):

protected void BindModel<TModel>(Controller controller, TModel viewModel)
{
    var controllerContext = SetUpControllerContext(controller, viewModel);
    var bindingContext = new ModelBindingContext
    {
        ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => viewModel, typeof(TModel)),
        ValueProvider = new JsonValueProviderFactory().GetValueProvider(controllerContext)
    };

    new DefaultModelBinder().BindModel(controller.ControllerContext, bindingContext);
    controller.ModelState.Clear();
    controller.ModelState.Merge(bindingContext.ModelState);
}

private static ControllerContext SetUpControllerContext<TModel>(Controller controller, TModel viewModel)
{
    var controllerContext = A.Fake<ControllerContext>();
    controller.ControllerContext = controllerContext;
    var json = new JavaScriptSerializer().Serialize(viewModel);
    A.CallTo(() => controllerContext.Controller).Returns(controller);
    A.CallTo(() => controllerContext.HttpContext.Request.InputStream).Returns(new MemoryStream(Encoding.UTF8.GetBytes(json)));
    A.CallTo(() => controllerContext.HttpContext.Request.ContentType).Returns("application/json");
    return controllerContext;
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.