[c#] Moq 프레임 워크를 사용하여 ModelState.IsValid를 모의하는 방법은 무엇입니까?

다음 ModelState.IsValid과 같이 Employee를 만드는 컨트롤러 작업 메서드를 확인 하고 있습니다.

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

    // Etc.
}

Moq Framework를 사용하여 단위 테스트 방법으로 모의하고 싶습니다. 나는 이것을 다음과 같이 조롱하려고했습니다.

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

그러나 이것은 내 단위 테스트 케이스에서 예외를 던집니다. 누구든지 여기서 나를 도울 수 있습니까?



답변

조롱 할 필요가 없습니다. 컨트롤러가 이미있는 경우 테스트를 초기화 할 때 모델 상태 오류를 추가 할 수 있습니다.

// 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();


답변

위의 솔루션에 대한 유일한 문제는 속성을 설정하면 실제로 모델을 테스트하지 않는다는 것입니다. 이 방법으로 컨트롤러를 설정했습니다.

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 개체는 모델의 유효성을 테스트하는 개체입니다. 이렇게하면 개체의 값을 설정하고 테스트 할 수 있습니다.


답변

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;
}


답변