Moqフレームワークを使用してModelState.IsValidをモックする方法は?


88

次のModelState.IsValidようなEmployeeを作成するコントローラーアクションメソッドをチェック インしています。

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

しかし、これは私のユニットテストケースで例外をスローします。誰かがここで私を手伝ってくれる?

回答:


140

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

// 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
@Newton、デフォルトではtrueです。本当のことをするために何も指定する必要はありません。偽のケースをヒットしたい場合は、私の回答に示すように、modelstateエラーを追加するだけです。
Darin Dimitrov

私見より良い解決策は、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 2012

2016年も素晴らしいソリューションのようです:)
Matt

2
このようなものと分離してモデルをテストする方がいいですか?stackoverflow.com/a/4331964/3198973
RubberDuck 2017年

2
これは賢い解決策ですが、@ RubberDuckに同意します。これが実際の分離された単体テストであるためには、モデルの検証は独自のテストでなければならず、コントローラーのテストには独自のテストが必要です。モデルがModelBinder検証に違反するように変更された場合、コントローラーのテストは失敗します。これは、コントローラーのロジックが壊れていないため、誤検知です。無効なModelStateDictionaryをテストするには、ModelState.IsValidチェックが失敗する偽のModelStateエラーを追加します。
xDaevax 2018年

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.