使用Moq的单元测试没有通过,对象为空,我错过了什么吗?

时间:2009-12-26 19:22:20

标签: c# unit-testing nhibernate moq

我要测试的类是我的ArticleManager类,特别是LoadArticle方法:

public class ArticleManager : IArticleManager
{
      private IArticle _article;

      public ArticleManger(IDBFactory dbFactory)
      {
            _dbFactory = dbFactory;
      }

      public void LoadArticle(string title)
      {
            _article = _dbFactory.GetArticleDAO().GetByTitle(title);

      }
}

我的ArticleDAO看起来像:

public class ArticleDAO : GenericNHibernateDAO<IArticle, int>, IArticleDAO
{
       public virtual Article GetByTitle(string title)
       {
           return Session.CreateCriteria(typeof(Article))
               .Add(Expression.Eq("Title", title))
               .UniqueResult<Article>();
       }
}

我的测试代码使用NUnitMoq

[SetUp]
public void SetUp()
{
        _mockDbFactory = new Mock<IDBFactory>();
        _mockArticleDao = new Mock<ArticleDAO>();

        _mockDbFactory.Setup(x => x.GetArticleDAO()).Returns(_mockArticleDao.Object);

        _articleManager = new ArticleManager(_mockDbFactory.Object);
}


[Test]
public void load_article_by_title()
{
     var article1 = new Mock<IArticle>();

     _mockArticleDao.Setup(x => x.GetByTitle(It.IsAny<string>())).Returns(article1.Object);

     _articleManager.LoadArticle("some title");

     Assert.IsNotNull(_articleManager.Article);
}

单元测试失败,对象_articleManager.Article返回NULL。

我做的一切都正确吗?

这是我的第一次单元测试之一,所以我可能遗漏了一些明显的东西?

我遇到的一个问题是,我想模仿IArticleDao,但由于ArticleDao类也继承自抽象类,如果我只是模拟了IArticleDao,那么GenericNHibernateDao中的方法是不可用的?

4 个答案:

答案 0 :(得分:5)

前言:我不熟悉使用Moq(Rhino Mocks用户),所以我可能会错过一些技巧。

我在努力遵循这里的一些代码;正如马克·西曼(Mark Seemann)所指出的那样,我不明白为什么这种情况甚至可以在目前的状你能仔细检查代码吗?

有一点需要注意的是,你正在向文章管理员注入IDBFactory的模拟。然后你进行链接调用:

_article = _dbFactory.GetArticleDAO().GetByTitle(title)

您尚未提供GetArticleDAO的实施方案。您只是嘲笑LoadByTitle调用之后发生的GetARticleDAO位。测试中模拟和链接调用的组合通常表明测试即将变得痛苦。

得墨忒耳法

突出点:Respect the Law of Demeter。 ArticleManager使用IDBFactory返回的IArticleDAO。除非IDBFactory做了非常重要的事情,否则你应该将IArticleDAO注入到ArticleManager中。

Misko雄辩地解释了为什么Digging Into Collaborators是一个坏主意。这意味着你有一个额外的挑战步骤来设置,也使API更加混乱。

此外,为什么将返回的文章作为字段存储在ArticleManager中?你可以回来吗?

如果可以进行这些更改,它将简化代码并使测试变得更容易。

您的代码将变为:

public class ArticleManager : IArticleManager
{
      private IArticleDAO _articleDAO

      public ArticleManger(IArticleDAO articleDAO)
      {
            _articleDAO = articleDAO;
      }

      public IArticle LoadArticle(string title)
      {
            return _articleDAO.GetByTitle(title);
      } 
}

然后你会有一个更简单的API,并且它更容易测试,因为嵌套已经消失了。

依赖持久性使测试更容易

在我是单元测试与持久性机制交互的代码的情况下,我通常使用repository pattern并创建手动,伪造的内存存储库来帮助进行测试。它们通常也很容易编写 - 它只是实现IArticleRepository接口的字典的包装器。

使用这种技术,您的ArticleManager可以使用伪造的持久性机制,其行为与数据库非常相似,以便进行测试。然后,您可以轻松地使用可帮助您以无痛方式测试ArticleManager的数据填充存储库。

模拟框架确实是很好的工具,但它们并不总是适合建立和验证复杂或连贯的交互;如果你需要在一次测试中模拟/存储多个东西(特别是嵌套的东西!),这通常表明测试是过度指定的,或者手动测试双重是更好的选择。

测试很难

......在我看来,如果你从模拟框架开始,那就更加困难了。我已经看到很多人因为在引擎盖下发生的“魔法”而与嘲弄框架结合在一起。因此,我一般主张远离他们until you're comfortable with hand-rolled stubs/mocks/fakes/spies etc

答案 1 :(得分:1)

正如您目前提供的代码,我无法看到它编译 - 有两个原因。

第一个可能只是一个疏忽,但是ArticleManager类没有Article属性,但我认为它只返回_article字段。

另一个问题是这行代码:

_mockArticleDao.Setup(x => x.GetByTitle(It.IsAny<string>())).Returns(article1.Object);

据我所知,这根本不应该编译,因为ArticleDAO.GetByTitle返回Article,但是你告诉它返回IArticle的实例(接口,不是具体的课程。)

您是否遗漏了代码说明中的内容?

无论如何,我怀疑问题在于Setup这个问题。如果您错误地指定了设置,它永远不会被调用,并且Moq默认为其默认行为,即返回该类型的默认值(即,对于引用类型为null)。

可以通过设置DefaultValue属性来改变BTW的行为:

 myMock.DefaultValue = DefaultValue.Mock;

但是,这不太可能解决你的这个问题,所以你能否解决我上面提到的问题,我相信我们可以弄清楚什么是错的。

答案 2 :(得分:0)

我不是Moq专家,但在我看来,问题在于你在嘲笑ArticleDAO,你应该嘲笑IArticleDAO。

这与你的问题有关:

  

我遇到的一个问题是,我想模仿IArticleDao,但由于ArticleDao类也继承自抽象类,如果我只是模拟了IArticleDao,那么GenericNHibernateDao中的方法是不可用的?

在mock对象中,您不需要从GenericNHibernateDao类继承的方法。您只需要模拟对象来提供参与测试的方法,即:GetByTitle。您通过模拟提供此方法的行为。

Moq不会模拟方法,如果它们已经存在于您尝试模拟的类型中。正如API docs

中所述
  

任何接口类型都可以用于模拟,但对于类,只能模拟抽象和虚拟成员。

具体来说,您的模拟GetByTitle将被忽略,因为模拟类型ArticleDao提供了此方法的(非抽象)实现。

因此,我的建议是模拟界面IArticleDao而不是类。

答案 3 :(得分:0)

正如Mark Seeman所提到的,由于.GetByTitle期望返回错误的类型,我无法将其编译为“as-is”,从而导致编译时错误。

在纠正了这个问题并添加了缺少的文章属性之后,测试通过了 - 让我认为你的问题的核心在翻译时会因为你在SO上写下来而丢失。

但是,鉴于您报告了一个问题,我想我会提到一种让Moq本身可以帮助您确定问题的方法。

您获得null _articleManager.Article的事实几乎可以肯定是因为没有匹配的期望.GetByTitle。换句话说,您指定的那个不匹配。

通过将模拟切换到严格模式,Moq会在调用没有匹配期望的时候引发错误。更重要的是,它将为您提供有关不匹配呼叫的完整信息,包括任何参数的值。有了这些信息,您应该能够立即找出您的期望不匹配的原因。

尝试使用“失败”模拟设置为严格运行测试,看看它是否为您提供解决问题所需的信息。

以下是对测试的重写,模拟为严格(折叠为单个方法以节省空间):

[Test]
public void load_article_by_title()
{
    var article1 = new Mock<Article>();
    var mockArticleDao = new Mock<ArticleDAO>(MockBehavior.Strict); //mock set up as strict
    var mockDbFactory = new Mock<IDBFactory>(MockBehavior.Strict); //mock set up as strict

    mockDbFactory.Setup(x => x.GetArticleDAO()).Returns(mockArticleDao.Object);
    mockArticleDao.Setup(x => x.GetByTitle(It.IsAny<string>())).Returns(article1.Object);

    var articleManager = new ArticleManager(mockDbFactory.Object);
    articleManager.LoadArticle("some title");

    Assert.IsNotNull(articleManager.Article);
}