【问题标题】:moq only one method in a class起订量只有一个类中的一种方法
【发布时间】:2014-08-27 18:06:00
【问题描述】:

我正在使用 moq.dll 当我模拟一个类(所有 IRepository 接口)时,我使用此行代码

   int state = 5;
   var rep = new Mock<IRepository>();
   rep.Setup(x => x.SaveState(state)).Returns(true);
   IRepository repository = rep.Object;

但在这种情况下,我模拟了存储库类中的所有函数。 然后将类库中的所有方法替换为 Mock dll 的方法设置

我想使用类库中定义的所有方法(真正的类)并只模拟一个函数(SaveState)

我该怎么做?有可能吗?

【问题讨论】:

  • 你知道你可以多次调用 rep.Setup 吗?
  • SaveState 是 virtual 吗?
  • SaveState 不是虚拟的。 Liath为什么要多次设置rep?我只想对这种方法使用设置。其他方法必须像现实世界一样工作
  • 如果您不控制存储库对每个调用的响应,那么它就不是真正的单元测试。为了做你想做的事,你可以让你的代表成为真正的IRepository 的代理,并模拟所有方法以将他们的请求传递给真正的IRepository。对我来说,这似乎是一件非常有代码味道的事情。
  • Mock.CallBase 属性控制这一点。但是,必须这样做通常是代码异味

标签: c# moq


【解决方案1】:

您可以创建一个真实存储库的实例,然后使用As&lt;&gt;() 获取所需的接口,然后您可以使用设置覆盖该接口,如下所示:

var mockRep = new Mock<RealRepository>(ctorArg1, ctorArg2, ...)
                     .As<IRepository>();
mockRep.Setup(x => x.SaveState(state)).Returns(true);

然后mockRep.Object 作为被测类的存储库依赖项。 请注意,您只能以这种方式模拟 Interface* 上的方法或虚拟方法。

更新:*这可能不适用于所有场景,因为.Setup 仅适用于 virtual 方法,而 C# 接口实现为 "virtual" and sealed by default .并且使用As() 将防止部分模拟行为。

因此看来RealRepository 具体类需要使用虚方法实现IRepository 接口才能使部分模拟成功,在这种情况下CallBase 可用于连接。

   public interface IRepo
   {
      string Foo();
      string Bar();
   }

   public class RealRepo : IRepo
   {
      public RealRepo(string p1, string p2) {Console.WriteLine("CTOR : {0} {1}", p1, p2); }
      // ** These need to be virtual in order for the partial mock Setups
      public virtual string Foo() { return "RealFoo"; }
      public virtual string Bar() {return "RealBar"; }
   }

   public class Sut
   {
      private readonly IRepo _repo;
      public Sut(IRepo repo) { _repo = repo; }

      public void DoFooBar()
      {
         Console.WriteLine(_repo.Foo());
         Console.WriteLine(_repo.Bar());
      }
   }


   [TestFixture]
   public class SomeFixture
   {
      [Test]
      public void SomeTest()
      {
        var mockRepo = new Mock<RealRepo>("1st Param", "2nd Param");
        // For the partially mocked methods
        mockRepo.Setup(mr => mr.Foo())
           .Returns("MockedFoo");
        // To wireup the concrete class.
        mockRepo.CallBase = true;
        var sut = new Sut(mockRepo.Object);
        sut.DoFooBar();
      }
   }

【讨论】:

  • 一切都很好,基本连线类的“sut”措辞对此有什么意义?
  • 是的,sutsystem under test 的一个相当广泛的术语,在单元测试术语中表示您正在测试的特定类(通过为其提供模拟依赖项)。
  • 最初,答案对我来说并不奏效。然而,再看一遍,我注意到Mock.CallBase 需要true 才能使答案起作用,正如@Ben Aaronson 在他的评论中提到的那样。
【解决方案2】:

我来到这个页面是因为我遇到了完全相同的问题:我需要模拟一个方法,该方法依赖于许多外部资源并且可以产生三个输出之一,同时让类的其余部分完成它的工作。不幸的是,上面提出的部分模拟方法不起作用。我真的不知道为什么它不起作用。但是,主要问题是即使您将断点放置在您想要的位置,您也无法在此类模拟类中进行调试。这不好,因为您可能真的需要调试一些东西。

所以,我使用了一个更简单的解决方案:将要模拟的所有方法声明为虚拟方法。然后从该类继承并编写单行模拟覆盖以返回您想要的内容,例如:

public class Repository
{
    /// <summary>
    /// Let's say that SaveState can return true / false OR throw some exception.
    /// </summary>
    public virtual bool SaveState(int state)
    {
        // Do some complicated stuff that you don't care about but want to mock.
        var result = false;

        return result;
    }

    public void DoSomething()
    {
        // Do something useful here and assign a state.
        var state = 0;

        var result = SaveState(state);
        // Do something useful with the result here.
    }
}

public class MockedRepositoryWithReturnFalse : Repository
{
    public override bool SaveState(int state) => false;
}

public class MockedRepositoryWithReturnTrue : Repository
{
    public override bool SaveState(int state) => true;
}

public class MockedRepositoryWithThrow : Repository
{
    public override bool SaveState(int state) => 
        throw new InvalidOperationException("Some invalid operation...");
}

就是这样。然后,您可以在单元测试期间使用您的模拟存储库,并且您可以调试任何您需要的东西。您甚至可以将保护级别保留在 public 以下,以免暴露您不想暴露的内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多