问题描述
||
我在控制器中有后期操作。代码如下
[HttpPost]
public ActionResult Create(Int64 id,FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (TryUpdateModel(data))
{
// Todo: Save data
return RedirectToAction(\"Edit\",\"Applications\",new { area = \"Applications\",id = id });
}
else
{
// Todo: update of the model has Failed,look at the error and pass back to the view
if (!ModelState.IsValid)
{
if (id != 0) Helper.ShowLeftColumn(data,id);
return View(\"Create\",data);
}
}
return RedirectToAction(\"Details\",\"Info\",new { area = \"Deals\",InfoId= id });
}
我为此编写了测试用例,如下所示
[TestMethod]
public void CreateTest_for_post_data()
{
var collection = GetApplicantDataOnly();
_controller.ValueProvider = collection.TovalueProvider();
var actual = _controller.Create(0,collection);
Assert.isinstanceOfType(actual,typeof(RedirectToRouteResult));
}
当我调试此单个测试用例时,测试用例通过了,因为条件
如果(TryUpdateModel(data))返回true,则转到if条件。
但是,当我从整个解决方案中调试测试用例时,该测试用例失败了,因为它进入了其他条件\“ if(TryUpdateModel(data))\”。
我不知道为什么。
请帮忙...
谢谢
解决方法
我遇到了类似的问题,只要您不需要使用
FormCollection
,它就会解决您的问题。
自从了解自动绑定功能的那一天以来,我从未使用过ѭ3。简而言之,自动绑定工作ѭ3可以为您完成很多工作,也就是说,它将根据FormCollection中的值设置模型对象,并尝试验证模型。它会自动执行此操作。您所要做的就是在ActionMethod中放置一个参数,它将自动使用FormCollection中的值填充其属性。您的动作签名将变为:
public ActionResult Create(Int64 id,SomeModel data)
现在,您根本不需要致电TryUpdateModel
。您仍然需要检查ModelState是否有效,以决定是否重定向或返回视图。
[HttpPost]
public ActionResult Create(Int64 id,SomeModel data)
{
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction(\"Edit\",\"Applications\",new { area = \"Applications\",id = id });
}
else
{
if (id != 0) Helper.ShowLeftColumn(data,id);
return View(\"Create\",data);
}
}
这不会在您的单元测试中引发异常,因此这是一个已解决的问题。但是,现在还有另一个问题。如果您使用上述代码运行应用程序,则可以正常运行。输入操作后,将验证您的模型,并返回正确的ActionResult(重定向或视图)。但是,当您尝试对两个路径进行单元测试时,即使模型无效,模型也会始终返回重定向。
问题在于,在进行单元测试时,该模型根本没有得到验证。而且由于ModelState在默认情况下是有效的,因此ModelState.IsValid
在您的单元测试中将始终返回true,因此即使模型无效,也将始终返回重定向。
解决方案:呼叫TryValidateModel
而不是ModelState.IsValid
。这将迫使您的单元测试来验证模型。这种方法的一个问题是,该模型将在您的单元测试中进行一次验证,而在您的应用程序中进行两次验证。这意味着发现的任何错误将在您的应用程序中记录两次。这意味着,如果您在视图中使用ValidationSummary
辅助方法,您将看到列出的某些重复消息。
如果这负担不了,可以在致电TryValidateModel
之前清除clear12ѭ。这样做有一些问题,因为您将丢失一些有用的数据,例如,如果清除ModelState
,则会丢失尝试的值,因此您可以只清除ModelState
中记录的错误。您可以通过深入研究ѭ12并清除存储在每个项目中的每个错误来这样做,如下所示:
protected void ClearModelStateErrors()
{
foreach (var modelState in ModelState.Values)
modelState.Errors.Clear();
}
我已将代码放在一个方法中,以便所有Action都可以重用它。我还添加了protected
关键字,以暗示这可能是放置在所有控制器派生自的BaseController中的有用方法,以便它们都可以访问此方法。
最终解决方案:
[HttpPost]
public ActionResult Create(Int64 id,SomeModel data)
{
ClearModelStateErrors();
if (ModelState.IsValid)
{
// TODO: Save data
return RedirectToAction(\"Edit\",data);
}
}
注意:我意识到我并未对根本问题有所了解。那是因为我不完全了解根本问题。如果您注意到失败的单元测试,它会因为ControllerContext
为空而抛出ArgumentNullException
而失败,并且将其传递给一个方法,如果ControllerContext
为空则抛出异常。 (用他们该死的防御程序来诅咒MVC团队)。
如果您尝试模仿ControllerContext
,您仍然会得到一个例外,这次是NullReferenceException
。有趣的是,异常的堆栈跟踪显示两个异常都以相同的方法发生,或者我应该说构造函数位于25位。我没有源代码的副本,因此我不知道是什么导致了异常,并且我还没有找到比上面提供的解决方案更好的解决方案。我个人不喜欢我的解决方案,因为为了单元测试的好处,我正在更改编写应用程序的方式,但是似乎没有更好的选择。我敢打赌,真正的解决方案将涉及模拟其他对象,但是我只是不知道什么。
同样,在任何人获得任何聪明主意之前,嘲笑ValueProvider都不是解决方案。它会停止异常,但是您的单元测试不会验证您的模型,并且您的ModelState
始终会报告该模型有效,即使模型无效也是如此。
,您可能需要稍微清理一下代码:
[HttpPost]
public ActionResult Create(int id,FormCollection collection)
{
var data = Helper.CreateEmptyApplicationsModel();
if (!ModelState.IsValid)
{
if (id != 0)
{
Helper.ShowLeftColumn(data,id);
}
return View(\"Create\",data);
}
if (TryUpdateModel(data))
{
return RedirectToAction(\"Edit\",id = id });
}
return RedirectToAction(\"Details\",\"Info\",new { area = \"Deals\",InfoId= id });
}
不要使用Int64
,而应使用int
。
至于失败的测试,我希望您的测试会一直失败,因为TryUpdateModel
将返回false。当您从单元测试运行代码时,控制器的控制器上下文不可用,因此“ 3”将失败。
您需要以某种方式伪造/模拟TryUpdateModel
,以使其实际上无法正常运行。相反,您“伪造”它会返回true。这里有一些帮助链接:
如何在不模拟使用UpdateModel的情况下对单元进行测试?
上面的SO答案显示了一个使用RhinoMocks的示例,它是一个免费的模拟框架。
或这个:
http://www.codecapers.com/post/ASPNET-MVC-Unit-Testing-UpdateModel-and-TryUpdateModel.aspx
, 调试测试并检查modelstate错误集合,tryupdatemodel遇到的所有错误都应该存在。