使用MVVM应用程序。每个ViewModel类都有一个构造函数,它接受一个Repository类,以便可以模拟它进行单元测试。
该应用程序旨在同时跨多个窗口进行操作。所以它包含一个数字" View"或"打开"样式方法,用于创建新的ViewModel并将它们放入新窗口。因为这些是通过UI触发的,所以它们通常位于现有的ViewModel中。例如:
public void ViewQuote(Quote quote)
{
if (quote.CreatedOn == null)
{
quote.CreatedOn = DateTime.Now;
}
NavigationHelper.NewWindow(this, new QuoteViewModel(quote, new Repository()));
}
现在,该流控制语句看起来值得测试,以确保使用null CreatedOn日期传递的引号被赋值为1。但是,我的测试失败了,因为虽然父ViewModel有一个模拟的存储库,但是NewWindow方法会在其中创建一个带有真实存储库的新ViewModel。然后,当它在该类的构造函数中使用时会抛出错误。
有两个明显的选择。
一种方法是将日期分配拉出到一个独立的功能中进行测试。这会起作用,但对于它自己的功能来说似乎过于简单了。此外,如果我在整个应用程序中执行此操作,则可能会产生过多碎片,从而易于阅读。
另一种方法是以某种方式更改ViewModels的构造函数代码,使其不直接使用Repository。这可能是一个选择,但它不可能适用于所有可能的情况。
或者有没有第三种方法可以更好地设计它,以便我可以将模拟的存储库传递给我的新ViewModel的构造函数?
答案 0 :(得分:3)
新的服务(或类似服务的对象,如存储库)是一种设计气味。您遇到的问题就是结果。
换句话说,您缺乏清晰明确的Composition Root。
唯一干净的解决方案是通过构造函数注入服务。存储库通常具有比应用程序本身更短的生命周期,因此在这种情况下,您将注入能够创建存储库的工厂。
请注意,明确的依赖树是很好的设计,但使用诸如Autofac之类的DI框架只是实现这种设计的一种技术解决方案。您可以完全解决问题并创建一个干净的组合根,而无需使用DI框架。
因此,虽然这可能需要做很多工作,但您应该重新设计应用程序以获得明确的组合根。否则,您将一遍又一遍地遇到小问题,尤其是在测试领域。
答案 1 :(得分:-1)
您可以通过在public Repository NewRepository
方法中添加ViewQuote
属性来解决此问题,将其更改为:
public void ViewQuote(Quote quote)
{
if (quote.CreatedOn == null)
{
quote.CreatedOn = DateTime.Now;
}
if(NewRepository == null) NewRepository = new Respository();
NavigationHelper.NewWindow(this, new QuoteViewModel(quote, NewRepository));
}
然后在模拟中,只需确保在对该位代码运行测试之前分配了公共NewRepository
属性。
不是很优雅,但在我看来它需要的变化最小。