使用TryUpdateModel时的asp.net核心MVC控制器单元测试 [英] asp.net core mvc controller unit testing when using TryUpdateModel

查看:57
本文介绍了使用TryUpdateModel时的asp.net核心MVC控制器单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在asp.net核心应用程序中,我有一对响应Edit操作的控制器方法.一个用于GET,它使用一个字符串参数作为实体ID:

In an asp.net core application, I have a pair of controller methods that respond to an Edit action. One for GET, which takes a string parameter for the entity id:

public async Task<IActionResult> Edit(string id)

,另一个用于接收更新后的实体值的POST:

and the other for receiving a POST of updated entity values:

[HttpPost]
[ActionName("Edit")]
[ValidateAntiForgeryToken]        
public async Task<IActionResult> EditSave(string id)

在可发布动作方法中,我调用

Inside the postable action method, I call

var bindingSuccess = await TryUpdateModelAsync(vm);

那很好.

现在,我正在尝试为此编写测试,但发现 TryUpdateModelAsync 需要 HttpContext 中的很多内容,并且需要充实控制器.我已经尝试过模拟这些,但是在查看了 TryUpdateModelAsync 的源代码之后,我意识到我实际上需要将所有内容都模拟成元数据,事实证明这并非直截了当.

Now, I'm trying to write a test for this, but am finding that TryUpdateModelAsync requires lots of things from HttpContext and the controller to be fleshed out. I've tried mocking those out, but after looking at the source code for TryUpdateModelAsync, I realize that I'd essentially need to mock everything down to the metadata, which isn't proving to be straightforward.

我想知道这种困难是否正在告诉我一些事情: TryUpdateModelAsync 使其难以测试,因此我应该重构控制器方法以使其不依赖于此帮助程序.相反,我可以为我的viewmodel方法添加另一个参数,并用 [FromBody] 装饰它,这样模型绑定将在出现时从post字段进行,但是我可以通过测试时查看模型.但是,我喜欢 TryUpdateModelAsync 方法,因为它完成了将post字段合并到我的视图模型中的繁重工作.我可以想到完成合并的另一种方法是编写自己的Merge方法.好的,没什么大不了的,但是我不想不必对每个实体都这样做(或者重新发明写基于反射的合并的轮子),真的,我想知道我是否错过了如何编写单元的内容对此进行测试.我可以像集成测试一样启动整个 TestServer ,但是我不确定这是正确的方向,并且感觉就像会使我的单元测试更加复杂.但是,在这种情况下也许是合理的?

I'm wondering if perhaps this difficulty is telling me something: TryUpdateModelAsync makes it hard to test, so I should refactor the controller method to not rely on this helper. Instead, I could add another parameter to the method for my viewmodel and decorate it with [FromBody], so the model binding would happen from the post fields when present, but I would be able to pass in a view model when testing. However, I like the TryUpdateModelAsync method, because it does the busy work of merging the post fields into my view model. The other way I can think to accomplish the merging is write my own Merge method. Okay, no big deal, but I'd prefer not to have to do this for each entity (or reinvent the wheel writing a reflection based merger) and really, I'm wondering if I just missed the boat on how to write a unit test against this. I could fire up a whole TestServer, like I do for integration tests, but I'm not sure this is the right direction and feels like I would just be complicating my unit tests further. However, maybe it is justified in this scenario?

我已经看到了适用于早期版本的.net mvc的答案,在该版本中,他们所需要做的就是模拟 IValueProvider 并将其附加到控制器,但是在.net核心中, TryUpdateModelAsync 已重新设计,并且需要更多的活动部件.

I've seen answers that worked with previous versions of .net mvc, where all they needed to do was mock an IValueProvider and attach it to the controller, but in .net core it appears the TryUpdateModelAsync was reworked and requires more moving parts.

总而言之,我看到三个选项:

In sum, I see three options:

  1. 继续嘲弄并删除所有 TryUpdateModelAsync 需求.看来这可能是一个死胡同到目前为止.
  2. 使用 TestServer 并从一点开始进行此测试使用 HttpClient
  3. 更高的高度
  4. 重构此方法以使用在视图模型参数上 [FromBody] ,然后编写我自己的合并每个实体的方法,并排完全 TryUpdateModelAsync
  1. Continue mocking and stubbing out all the pieces that TryUpdateModelAsync needs. This might be a dead end, it seems to be so far.
  2. Use TestServer and make this test from a little higher altitude using an HttpClient
  3. Refactor this method to use [FromBody] on a view model parameter, then write my own merge methods for each entity, there by side stepping TryUpdateModelAsync altogether

这些都有其缺点,所以我希望这个列表中有第4个条目,因为我很无知,所以我没有看到它.

These all have their drawbacks, so I'm hoping there's a 4th entry to this list that I don't see because I am ignorant.

推荐答案

查看ControllerBase的源代码后,我注意到有问题的方法依赖于 static 方法 ModelBindingHelper.TryUpdateModelAsync (严重!!!?我认为我们现在已经有了进一步的发展.)

After looking at the source for ControllerBase, I noticed that the troublesome method in question relies on the static method ModelBindingHelper.TryUpdateModelAsync (seriously!!!!? I thought we were more evolved by now.)

正如您已经痛苦地发现的那样,这使得对控制器进行测试有些麻烦.

This as you have already painfully discovered makes testing your controller some what of a bother.

我想知道这种困难是否正在告诉我一些东西

I'm wondering if perhaps this difficulty is telling me something

别再怀疑了.这是.:)

well stop wondering. It is. :)

这是您可能已经忽略的另一种选择.抽象/使这种困难回到它产生的深处.

Here is another option you may have over looked. Abstract/Adapt away that difficulty back to the depths from which it came.

public interface IModelBindingHelperAdaptor {
    Task<bool> TryUpdateModelAsync<TModel>(ControllerBase controller, TModel model) where TModel : class;
}

一个实现可以看起来像这样

an implementation can look like this

public class DefaultModelBindingHelperAdaptor : IModelBindingHelperAdaptor {
    public virtual Task<bool> TryUpdateModelAsync<TModel>(ControllerBase controller, TModel model) where TModel : class {
        return controller.TryUpdateModelAsync(model);
    }
}

IModelBindingHelperAdaptor 作为依赖项注入到您的控制器中,并使其调用恶魔生成的方法.

Inject the IModelBindingHelperAdaptor into your controller as a dependency and let it call the demon spawned method.

var bindingSuccess = await modelBindingHelper.TryUpdateModelAsync(this, vm);

您现在可以在没有所有紧密耦合的情况下自由模拟抽象,这是我认为他们应该首先完成的事情.

You are now free to mock your abstraction without all the tight coupling, which is what I think they should have done in the first place.

现在,我假设您已经知道要在启动时设置必要的内容以使上述建议生效,因此对于您来说,启动并运行它并不是一项艰巨的任务.

Now I assume you already know to setup the necessary things in your startup to allow for the above suggestion to work so it should not be a difficult task for you to get that up and running.

这篇关于使用TryUpdateModel时的asp.net核心MVC控制器单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆