asp.net core mvc控制器单元测试时使用TryUpdateModel

本文关键字:TryUpdateModel 单元测试 控制器 net core mvc asp | 更新日期: 2023-09-27 18:05:07

在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 action方法中,我调用

var bindingSuccess = await TryUpdateModelAsync(vm);

这很好。

现在,我正试图为此编写一个测试,但我发现TryUpdateModelAsync需要HttpContext和控制器的许多东西来充实。我试过模拟这些,但在看了TryUpdateModelAsync的源代码后,我意识到我基本上需要模拟所有的元数据,这并不是直接的。

我想知道如果也许这个困难是告诉我的东西:TryUpdateModelAsync使它难以测试,所以我应该重构控制器方法,不依赖于这个助手。相反,我可以为我的视图模型的方法添加另一个参数,并用[FromBody]修饰它,这样,当post字段出现时,模型绑定就会发生,但在测试时,我将能够传入视图模型。但是,我喜欢TryUpdateModelAsync方法,因为它完成了将post字段合并到视图模型中的繁忙工作。我能想到的另一种完成合并的方法是编写我自己的Merge方法。好吧,没什么大不了的,但我不希望为每个实体都这样做(或者重新发明轮子,编写基于反射的合并),真的,我想知道我是否错过了如何编写针对此的单元测试的船。我可以启动一个完整的TestServer,就像我为集成测试所做的那样,但我不确定这是正确的方向,感觉我只会进一步复杂化我的单元测试。然而,也许在这种情况下是合理的?

我已经看到了与以前版本的。net mvc工作的答案,他们所需要做的就是模拟IValueProvider并将其附加到控制器上,但在。net核心中,TryUpdateModelAsync似乎被重新制作,需要更多的移动部件。

总的来说,我看到了三个选项:
  1. 继续嘲笑和存根所有的碎片TryUpdateModelAsync需求。这似乎是个死胡同
  2. 使用TestServer,使这个测试从一点点使用HttpClient
  3. 更高的高度
  4. 重构此方法以使用[FromBody]上的视图模型参数,然后写我自己的合并每个实体的方法,在那里进行并行步进TryUpdateModelAsync完全

这些都有它们的缺点,所以我希望在这个列表中有第四个我没有看到的条目,因为我很无知。

asp.net core mvc控制器单元测试时使用TryUpdateModel

查看了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作为一个依赖项注入到你的控制器中,并让它调用demon衍生方法。

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

您现在可以自由地模拟您的抽象,而不需要所有的紧密耦合,这是我认为他们首先应该做的。

现在我假设你已经知道在你的启动中设置必要的东西,以允许上面的建议工作,所以它应该不是一个困难的任务,让它启动并运行