2016-08-31 120 views
1

在一个asp.net核心应用程序,我有一对控制器方法响应编辑操作。一个GET,这需要一个字符串参数为实体ID:asp.net核心mvc控制器单元测试时使用TryUpdateModel

public async Task<IActionResult> Edit(string id) 

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

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

里面的过帐操作方法,我称之为

var bindingSuccess = await TryUpdateModelAsync(vm); 

而且工作正常。

现在,我试图为此编写一个测试,但我发现TryUpdateModelAsync需要从HttpContext和控制器充实大量的东西。我试着嘲笑这些,但在查看TryUpdateModelAsync的源代码后,我意识到我基本上需要将所有东西都嘲弄到元数据,而这并不是简单明了的。

我想知道是否这个难题告诉了我一些事情:TryUpdateModelAsync使它很难测试,所以我应该重构控制器方法而不依赖这个帮助器。相反,我可以为我的viewmodel的方法添加另一个参数,并使用[FromBody]对其进行装饰,因此模型绑定在发布时会从发布字段发生,但在测试时我可以传入视图模型。但是,我喜欢TryUpdateModelAsync方法,因为它会将post字段合并到我的视图模型中。另一种我能想到完成合并的方式是编写我自己的Merge方法。好吧,没什么大不了的,但我宁愿不必为每个实体做这件事(或者重新开始编写基于反思的合并),真的,我想知道我是否错过了如何编写一个单元对此进行测试。我可以启动整个TestServer,就像我为集成测试所做的那样,但我不确定这是否是正确的方向,感觉就像我会进一步复杂化单元测试一样。但是,在这种情况下,这可能是合理的吗?

我已经看到了与以前版本的.NET MVC的,所有他们需要做的是模拟一个IValueProvider并将其连接到控制器,但在.NET的核心它出现在TryUpdateModelAsync被重新设计,并需要更多的工作答案移动部件。

总之,我看到三个选项:

  1. 继续嘲讽和删空所有的作品是 TryUpdateModelAsync需求。这可能是一个死路一条,似乎到目前为止是 。
  2. 使用TestServer和使用HttpClient
  3. 重构这个方法来通过侧跨 在视图模型参数使用 [FromBody],然后我自己写的合并 方法为每个实体,也使从一个小 更高的高度本次测试TryUpdateModelAsync

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

回答

2

看源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); 

您现在可以自由地嘲笑你的抽象没有所有的紧耦合,这是我认为他们应该在第一时间做了。

现在我假设你已经知道在你的启动中设置必要的事情以允许上面的建议工作,所以对你来说启动和运行应该不是一件困难的事情。

+1

嗯,我喜欢这样的解决方案 - 如果你需要模拟它,包起来并注入。我同意,ControllerBase不能很好地遵守显式依赖关系。我去了另一个方向,并充实了HttpContext,并在没有抛出异常的情况下遇到了一个问题,但模型没有更新。我会留下另一个不同的问题。这似乎提供了一个可行的第四选择:) – Erikest

1

你可以尝试MyTested.AspNetCore.Mvc - 它为你准备了所有必要的物体,这样你就不需要模拟宇宙。考虑到这一点,在TryUpdateModel代码将在一个简单的测试工作是这样的:

MyMvc 
    .Controller<MyController>() 
    .Calling(c => c.MyAction()) 
    .ShouldReturn() 
    .View(); 

此外,它很容易定制:

MyMvc 
    .Controller<MvcController>() 
    .WithOptions(options => options 
     .For<AppSettings>(settings => settings.Cache = true)) 
    .WithSession(session => session 
     .WithEntry("Session", "SessionValue")) 
    .WithDbContext(db => db.WithEntities(entities => entities 
     .AddRange(SampleDataProvider.GetModels()))) 
    .Calling(c => c.SomeAction()) 
    .ShouldHave() 
    .MemoryCache(cache => cache 
     .ContainingEntry(entry => entry 
      .WithKey("CacheEntry") 
      .WithSlidingExpiration(TimeSpan.FromMinutes(10)) 
      .WithValueOfType<CachedModel>() 
      .Passing(a => a.Id == 1))) 
    .AndAlso() 
    .ShouldReturn() 
    .View() 
    .WithModelOfType<ResponseModel>() 
    .Passing(m => m.Id == 1); 
相关问题