Moq:高级模拟设置

我对Moq比较陌生,并且有这个复杂的案例来模拟,并且有点困难。 我希望有经验的Moq用户可以就此提出建议:

在我的ViewModel中,ctor调用这个加载方法:

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

正如你所看到的,我已经为每个服务调用创建了两个Setups,但是由于它们的回调和顺序依赖性,它非常容易混淆。

例如,如果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与可以侦听属性更改事件的观察者/控制器相关联,或者在需要新数据时收到通知。 您可能完全可以从视图模型中删除数据服务作为依赖项。
  • 链接地址: http://www.djcxy.com/p/35335.html

    上一篇: Moq: Advanced Mock Setup

    下一篇: How to verify that method was NOT called in Moq?