在asp.net核心应用程序中,我有一对响应Edit操作的控制器方法。一个用于GET,它接受实体id的字符串参数:
public async Task<IActionResult> Edit(string id)
和另一个用于接收已更新实体值的POST:
[HttpPost]
[ActionName("Edit")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditSave(string id)
在postable动作方法中,我调用
var bindingSuccess = await TryUpdateModelAsync(vm);
这很好用。
现在,我正在尝试为此编写测试,但我发现TryUpdateModelAsync
需要HttpContext
的许多内容和控制器才能充实。我尝试过嘲笑这些,但在查看TryUpdateModelAsync
的源代码后,我意识到我基本上需要将所有内容都模拟到元数据中,而这些元数据并未证实直截了当。
我想知道这个难度是否告诉了我一些事情:TryUpdateModelAsync
使得测试变得困难,所以我应该重构控制器方法而不依赖于这个帮助器。相反,我可以为我的viewmodel添加另一个参数并使用[FromBody]
进行装饰,因此模型绑定将在post字段出现时发生,但我可以在测试时传入视图模型。但是,我喜欢TryUpdateModelAsync
方法,因为它忙于将post字段合并到我的视图模型中。我能想到完成合并的另一种方法是编写自己的Merge方法。好吧,没什么大不了的,但是我不想为每个实体做这件事(或者重新编写一个基于反射的合并的轮子)真的,我想知道我是否只是错过了如何写这个单元测试。我可以启动整个TestServer
,就像我进行集成测试一样,但是我不确定这是正确的方向,感觉我只会让我的单元测试变得更复杂。但是,在这种情况下,这可能是合理的吗?
我已经看到了与以前版本的.net mvc一起使用的答案,他们需要做的只是模拟IValueProvider
并将其附加到控制器,但在.net核心中它出现{ {1}}经过重新设计,需要更多移动部件。
总之,我看到三个选项:
TryUpdateModelAsync
需要。这似乎是一个死胡同
到目前为止。TryUpdateModelAsync
并稍稍进行此测试
使用TestServer
HttpClient
在视图模型参数上,然后编写我自己的合并
每个实体的方法,一边踩着一边
完全[FromBody]
这些都有它们的缺点,所以我希望这个列表中的第4个条目是我看不到的,因为我是无知的。
答案 0 :(得分:4)
在查看ControllerBase的源代码后,我注意到有问题的方法依赖于静态方法ModelBindingHelper.TryUpdateModelAsync
(严重!!!!?我以为我们是现在更进化。)
正如你已经痛苦地发现的那样,让你的控制器测试一些麻烦。
我想知道这个困难是否告诉了我一些事情
好好停止疑惑。它是。 :)
这是您可能已经看过的另一个选项。摘要/将这种困难转移到它所来自的深处。
public interface IModelBindingHelperAdaptor {
Task<bool> TryUpdateModelAsync<TModel>(ControllerBase controller, TModel model) where TModel : class;
}
实现可以看起来像这样
public class DefaultModelBindingHelperAdaptor : IModelBindingHelperAdaptor {
public virtual Task<bool> TryUpdateModelAsync<TModel>(ControllerBase controller, TModel model) where TModel : class {
return controller.TryUpdateModelAsync(model);
}
}
将IModelBindingHelperAdaptor
作为依赖项注入控制器,并让它调用恶魔衍生的方法。
var bindingSuccess = await modelBindingHelper.TryUpdateModelAsync(this, vm);
你现在可以自由地模仿你的抽象而没有所有的紧密耦合,这是我认为他们应该首先做的。
现在我假设您已经知道在启动时设置必要的东西以允许上述建议起作用,因此启动并运行它不应该是一项艰巨的任务。