web-dev-qa-db-ja.com

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

しかし、これは私のユニットテストケースで例外をスローします。誰でもここで私を助けることができますか?

83
Mazen

モックする必要はありません。既にコントローラーがある場合は、テストを初期化するときにモデル状態エラーを追加できます。

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

上記のソリューションで私が抱えている唯一の問題は、属性を設定してもモデルを実際にテストしないということです。このようにコントローラーをセットアップします。

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オブジェクトは、モデルの有効性をテストするオブジェクトです。このようにして、オブジェクトの値を設定してテストすることができます。

13
uadrive

uadriveの答えは私に道を歩みましたが、まだいくつかのギャップがありました。 new NameValueCollectionValueProvider()への入力にデータがない場合、モデルバインダーはコントローラーをmodelオブジェクトではなく空のモデルにバインドします。

それは問題ありません-モデルをNameValueCollectionとしてシリアル化し、それをNameValueCollectionValueProviderコンストラクターに渡すだけです。まあ、そうではありません。残念ながら、私のモデルにはコレクションが含まれており、NameValueCollectionValueProviderはコレクションとうまく動作しないため、私の場合はうまくいきませんでした。

ただし、JsonValueProviderFactoryがここで助けになります。コンテンツタイプ"application/json "を指定し、シリアル化されたJSONオブジェクトをリクエストの入力ストリームに渡す限り、DefaultModelBinderで使用できます(この入力ストリームはメモリストリームであるため、注意してください、メモリストリームは外部リソースを保持しないため、そのままにしておいても構いません):

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;
}
2
Rob Lyndon