Moq:高级模拟设置

问题描述:

我对Moq比较新,有这个复杂的案例来模拟,有点卡住了。我希望有经验的起订量的用户可以咨询我关于这个:Moq:高级模拟设置

在我的视图模型的构造函数调用此方法负荷:

public void LoadCategories() 
     { 
      Categories = null; 
      BookDataService.GetCategories(GetCategoriesCallback); 
     } 

我想显然嘲笑服务。但是由于该服务的方法是无效的,并且返回总是通过回调,所以对我来说太复杂了。

private void GetCategoriesCallback(ObservableCollection<Category> categories) 
     { 
      if (categories != null) 
      { 
       this.Categories = categories; 
       if (Categories.Count > 0) 
       { 
        SelectedCategory = Categories[0]; 
       } 
       LoadBooksByCategory(); 
      } 
     } 

由于这还不够糟糕,你可以看到有一个名为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]; 
       } 
      } 
     } 

所以现在我的模拟设置:

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

,你可以请参阅我为每个服务调用创建了两个设置,但由于它们的回调和顺序依赖性,它非常混乱。

例如,如果viewmodel中的Selectedcategory属性保持为null,则不会调用第二个服务调用GetBooksByCategory()。但我唯一可以嘲笑的只是注入视图模型的服务。那么我怎么通过我的回调来影响viewmodel呢? :) 是否有意义?

最后,我期待ObservableCollection图书被实例化,并可能用一些测试数据填充(我在这里没有做,我很高兴如果它至少被实例化,以便我可以测试添加一个新的书到空集合)

那就是这个想法。一旦我能理解这一点,我想我理解了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); 
    } 
} 

但是,每当测试变得过于复杂或需要“先进”的逻辑,它往往是一个报警的东西可能是错误。如果ViewModel由于依赖性而无法实例化,这对我来说是一个交易断路器。

在您的示例中,您将创建两个依赖项(TestDomain和Context)以创建您的模拟BookDataService。这表明尽管您可以为您的服务创建虚拟替身,但您并未完全脱离其实施。

几个选项来考虑:

  • 你可能要为大家介绍的接口来包装现有的服务。这肯定会解决viewmodel实例化问题,并可能让您更容易使用API​​。但是,这不会解决视图模型中的后退逻辑。
  • 将加载逻辑外部化为另一个可测试组件。例如,将您的viewmodel与可以侦听属性更改事件的观察者/控制器相关联,或者在需要新数据时收到通知。您可能完全可以从视图模型中删除数据服务作为依赖项。
+0

非常感谢。我发现我的错误。还有一件事,会说mockService.VerifyAll();是单元测试的安排,行为或声明部分的一部分?我在什么阶段验证所有的模拟? – Houman 2011-02-15 12:31:08