私はチェックしています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
がここで助けになります。コンテンツタイプ"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;
}