Hou*_*man 3 .net unit-testing moq
我对Moq来说比较新,并且有这个复杂的案例可以模拟并且有点困难.我希望有经验的Moq用户可以就此提出建议:
在我的ViewModel中,ctor正在调用这个load方法:
public void LoadCategories()
{
Categories = null;
BookDataService.GetCategories(GetCategoriesCallback);
}
Run Code Online (Sandbox Code Playgroud)
我想明显嘲笑服务.但是由于服务的方法是无效的,并且返回总是通过回调,因此对我来说太复杂了.
private void GetCategoriesCallback(ObservableCollection<Category> categories)
{
if (categories != null)
{
this.Categories = categories;
if (Categories.Count > 0)
{
SelectedCategory = Categories[0];
}
LoadBooksByCategory();
}
}
Run Code Online (Sandbox Code Playgroud)
因为这还不够糟糕,正如你所看到的,在调用LoadBooksByCategory()中有另一个LoadMethod
public void LoadBooksByCategory()
{
Books = null;
if (SelectedCategory != null)
BookDataService.GetBooksByCategory(GetBooksCallback, SelectedCategory.CategoryID, _pageSize);
}
private void GetBooksCallback(ObservableCollection<Book> books)
{
if (books != null)
{
if (Books == null)
{
Books = books;
}
else
{
foreach (var book in books)
{
Books.Add(book);
}
}
if (Books.Count > 0)
{
SelectedBook = Books[0];
}
}
}
Run Code Online (Sandbox Code Playgroud)
所以现在我的Mock设置:
bool submitted = false;
Category selectedCategory = new Category{CategoryID = 1};
ObservableCollection<Book> books;
var mockDomainClient = new Mock<TestDomainClient>();
var context = new BookClubContext(mockDomainClient.Object);
var book = new Book
{
...
};
var entityChangeSet = context.EntityContainer.GetChanges();
var mockService = new Mock<BookDataService>(context);
mockService.Setup(s => s.GetCategories(It.IsAny<Action<ObservableCollection<Category>>>()))
.Callback<Action<ObservableCollection<Category>>>(action => action(new ObservableCollection<Category>{new Category{CategoryID = 1}}));
mockService.Setup(s => s.GetBooksByCategory(It.IsAny<Action<ObservableCollection<Book>>>(), selectedCategory.CategoryID, 10))
.Callback<Action<ObservableCollection<Book>>>(x => x(new ObservableCollection<Book>()));
//Act
var vm = new BookViewModel(mockService.Object);
vm.AddNewBook(book);
vm.OnSaveBooks();
//Assert
EnqueueConditional(() => vm.Books.Count > 0);
EnqueueCallback(() => Assert.IsTrue(submitted));
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我为每个服务调用创建了两个安装程序,但由于它们的回调和顺序依赖性,它非常混乱.
例如,如果viewmodel中的Selectedcategory属性保持为null,则永远不会调用第二个服务调用GetBooksByCategory().但我唯一可以在这里模拟的就是注入viewmodel的服务.那么我如何通过我的回调影响viewmodel中的内容呢?:) 是否有意义?
最后,我期待ObservableCollection Books被实例化,并且可能填充了一些测试数据(我在这里没有这样做,如果它至少被实例化,我很高兴,所以我可以测试添加一本新书到空采集)
这个想法.一旦我理解了这一点,我想我已经理解了Moq.:)
从Moq的角度来看,您所做的一切在技术上都是正确的.您正在使用Moq的回调机制,该机制通常用于检查入站参数,但在这种情况下,您将调用自定义逻辑来模拟服务的功能.如果将Mocks配置为返回正确的值,则应该能够在演示模型中练习逻辑.您需要使用不同返回值的多个测试来正确执行所有执行路径.在你看来,这会让人感到困惑.
你可以通过创建一个有助于定义模拟的实用程序类来清理一些东西.这是一个粗略的例子,它在你的测试中采用了一些疯狂的管道并将其封装了一下:
public class BookClubContextFixtureHelper
{
Mock<BookDataService> _mockService;
ObservableCollection<Category> _categories;
public BookClubContextFixtureHelper()
{
// initialize your context
}
public BookDataService Service
{
get { return _mockService.Object; }
}
public void SetupCategories(param Category[] categories)
{
_categories = new ObservableCollection<Category>(categories);
_mockService
.Setup( s => s.GetCategories( DefaultInput() )
.Callback( OnGetCategories )
.Verifiable();
}
public void VerifyAll()
{
_mockService.VerifyAll();
}
Action<ObservableCollection<Category>> DefaultInput()
{
return It.IsAny<Action<ObservableCollection<Category>>>();
}
void OnGetCategories(Action<ObservableCollection<Category>> action)
{
action( _categories );
}
}
Run Code Online (Sandbox Code Playgroud)
但是,每当测试变得过于复杂或需要"高级"逻辑时,通常会发出警告,表明某些事情可能是错误的.如果ViewModel无法实例化,因为依赖项对我来说是一个交易破坏者.
在您的示例中,您将创建两个依赖项(TestDomain和Context)以创建Mock BookDataService.这表明虽然您可以为您的服务创建虚拟替身,但您并没有完全脱离其实施.
几个选项可供考虑:
| 归档时间: |
|
| 查看次数: |
3683 次 |
| 最近记录: |