Moq çerçevesini kullanarak ModelState.IsValid ile nasıl alay edilir?


91

Şu şekilde ModelState.IsValidbir Çalışan oluşturan denetleyici eylem yöntemimi kontrol ediyorum :

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

    // Etc.
}

Moq Framework kullanarak birim test yöntemimde alay etmek istiyorum. Bununla dalga geçmeye çalıştım:

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

Ancak bu benim birim test durumumda bir istisna oluşturuyor. Biri bana yardım edebilir mi?

Yanıtlar:


142

Dalga geçmenize gerek yok. Zaten bir denetleyiciniz varsa, testinizi başlatırken bir model durum hatası ekleyebilirsiniz:

// 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'i doğru duruma gelmesi için nasıl ayarlayabiliriz? ModelState bir ayarlayıcıya sahip değildir ve bu nedenle aşağıdakileri yapamayız: _controllerUnderTest.ModelState.IsValid = true. Bu olmazsa, çalışanı
vurmaz

4
@Newton, varsayılan olarak doğrudur. Gerçek duruma ulaşmak için hiçbir şey belirtmenize gerek yok. Yanlış vakaya ulaşmak istiyorsanız, cevabımda gösterildiği gibi bir model durumu hatası eklemeniz yeterlidir.
Darin Dimitrov

IMHO Daha iyi çözüm, mvc konveyör kullanmaktır. Bu şekilde denetleyicinizin daha gerçekçi davranışını elde edersiniz, kaderine model doğrulaması sunmalısınız - öznitelik doğrulamaları. Aşağıdaki gönderi bunu açıklıyor ( stackoverflow.com/a/5580363/572612 )
Vladimir Shmidt

13

Yukarıdaki çözümle ilgili sahip olduğum tek sorun, öznitelikleri ayarlarsam modeli gerçekten test etmemesidir. Denetleyicimi bu şekilde kurdum.

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 nesnesi, modelin geçerliliğini test eden nesnedir. Bu şekilde nesnenin değerlerini ayarlayabilir ve test edebilirim.


1
Çok güzel, tam da aradığım şey buydu. Bunun gibi eski bir soruyu kaç kişinin gönderdiğini bilmiyorum ama benim için sana değer verdi. Teşekkürler.
W.Jackson

Hala 2016'da harika bir çözüm gibi görünüyor :)
Matt

2
Modeli böyle bir şeyle tek başına test etmek daha iyi değil mi? stackoverflow.com/a/4331964/3198973
RubberDuck

2
Bu akıllıca bir çözüm olsa da @RubberDuck'a katılıyorum. Bunun gerçek, izole bir birim testi olması için, modelin doğrulanması kendi testi olmalıdır, kontrolörün testinin kendi testleri olmalıdır. Model ModelBinder doğrulamasını ihlal edecek şekilde değişirse, kontrol cihazı testiniz başarısız olur ve bu, kontrolör mantığı bozulmadığı için yanlış bir pozitiftir. Geçersiz bir ModelStateDictionary test etmek için, ModelState.IsValid kontrolünün başarısız olması için sahte bir ModelState hatası eklemeniz yeterlidir.
xDaevax

2

uadrive'ın cevabı beni yolun bir parçası haline getirdi, ancak yine de bazı boşluklar vardı. Girişte herhangi bir veri olmadan new NameValueCollectionValueProvider(), model bağlayıcı denetleyiciyi modelnesneye değil boş bir modele bağlayacaktır .

Sorun değil - modelinizi bir olarak serileştirin NameValueCollectionve sonra bunu kurucuya aktarın NameValueCollectionValueProvider. Pek iyi değil. Ne yazık ki benim durumumda işe yaramadı çünkü modelim bir koleksiyon içeriyor ve koleksiyonlarla iyi NameValueCollectionValueProvideroynamıyor.

Yine JsonValueProviderFactoryde burada kurtarmaya geliyor. DefaultModelBinderBir içerik türü belirlediğiniz "application/jsonve serileştirilmiş JSON nesnenizi isteğinizin giriş akışına ilettiğiniz sürece kullanılabilir (Bu giriş akışı bir bellek akışı olduğundan, onu bir bellek olarak kullanılmadan bırakılmasının sorun olmayacağını lütfen unutmayın. akış herhangi bir harici kaynağı tutmaz):

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;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.