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


90

다음 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);

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

답변:


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에는 setter가 없으므로 다음을 수행 할 수 없습니다. _controllerUnderTest.ModelState.IsValid = true. 그 없이는 직원 명중하지 않을 것이다
카란

4
@Newton, 기본적으로 사실입니다. 실제 사례를 맞추기 위해 아무것도 지정할 필요가 없습니다. 잘못된 경우를 치려면 내 대답에 표시된 것처럼 modelstate 오류를 추가하면됩니다.
Darin Dimitrov 2012-07-25

IMHO Better 솔루션은 mvc 컨베이어를 사용하는 것입니다. 이러한 방식으로 컨트롤러의보다 현실적인 동작을 얻을 수 있으며, 운명에 대한 모델 유효성 검사 (속성 유효성 검사)를 제공해야합니다. 게시물 아래이 (묘사하고 stackoverflow.com/a/5580363/572612를 )
블라디미르 Shmidt

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
아주 좋아, 이것이 바로 내가 찾던 것입니다. 얼마나 많은 사람들이 이와 같은 오래된 질문에 글을 올렸는지 모르겠지만 그것은 나에게 가치가있었습니다. 감사.
W.Jackson

2016 년에도 여전히 훌륭한 솔루션처럼 보입니다 :)
Matt

2
이런 식으로 모델을 따로 테스트하는 것이 더 낫지 않습니까? stackoverflow.com/a/4331964/3198973
RubberDuck 2017 년

2
이것은 영리한 솔루션이지만 @RubberDuck에 동의합니다. 이것이 실제 격리 된 단위 테스트가 되려면 모델 유효성 검사는 자체 테스트 여야하며 컨트롤러 테스트에는 자체 테스트가 있어야합니다. ModelBinder 유효성 검사를 위반하도록 모델이 변경되면 컨트롤러 테스트가 실패합니다. 이는 컨트롤러 로직이 손상되지 않았기 때문에 거짓 긍정입니다. 잘못된 ModelStateDictionary를 테스트하려면 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;
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.