构造函数使用模拟对象,如何单独测试方法?

时间:2011-01-22 18:25:20

标签: c# unit-testing moq

我有一个看起来像这样的课程:

class MyClass {

  private IDependency dep;

  public MyClass(IDependency dep) {
    this.dep = dep;
    this.dep.Reset();
  }

  public void Clear() {
    this.dep.Reset();
  }
}

如何测试在Clear方法中正确调用Reset方法而忽略构造函数的作用?

我的Moq测试看起来像这样:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

dep.Setup(s => s.Reset());

MyClass myclass = new MyClass(dep.Object);
myclass.Clear():

state.Verify(s => s.Reset(), Times.Exactly(1));

失败,因为重置已被调用两次(一次在构造函数中,一次在Clear方法中)。

5 个答案:

答案 0 :(得分:2)

我希望有更好的方法,但模拟会记录所有对Reset的调用,因此使用标准Verify调用将始终返回2.以下维护一个单独的计数器,这不是很优雅。如果有一种内置的方式用Moq做这件事,我很想知道。

int clearResetCount = 0;

Mock<IDependency> dep = new Mock<IDependency>();

MyClass myclass = new MyClass(dep.Object);

dep.Setup(s => s.Reset()).Callback(() => clearResetCount++);

Assert.AreEqual(0, clearResetCount, "Expected clear reset count - before.");

myclass.Clear();

Assert.AreEqual(1, clearResetCount, "Expected clear reset count - after.");

答案 1 :(得分:2)

正如其他人所建议的那样,你可以推出自己的模拟,或者你可以设置一些对依赖的期望。

例如,您可以验证您的方法是否已被调用:

var mock = new Mock<IDependency>();
var subject = new MyClass(mock.Object);

subject.Clear();

mock.Verify( dep => dep.Reset(), Times.AtMost(2));

然而值得指出的是work within the constructor is a known code smell,当你尝试编写测试时,这种气味会加剧。

构造函数需要在依赖项上调用此方法这一事实表明此对象知道有关依赖项的实现细节的过多信息。这违反了开放封闭原则,并且在您不希望在初始化时调用Reset方法的情况下将您关闭。

还要考虑使用MyClass具体对象作为伪参数的任何类或测试都需要初始化Mock,否则您将获得NullReferenceException。这会增加编写测试的开销,并增加一定程度的脆弱性,相当于测试中的长期维护和漏报。解决这个问题的唯一方法就是让所有东西都成为一个虽然有效但不是最好的长期战略的界面。

根据http://googletesting.blogspot.com/2009/07/separation-anxiety.html,使用Factory会减少一些耦合,并打开你更好地重用这个对象。

答案 2 :(得分:1)

我遇到了同样的问题。

执行以下操作以使模拟仅记录Clear方法的行为:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

MyClass myclass = new MyClass(dep.Object);

// setting up the mock just before calling the method under test
// will ignore any prior call to IDependency.Reset
int resetTimes = 0;
dep.Setup(s => s.Reset()).Callback(() => resetTimes++);

myclass.Clear();

mocks.VerifyAll();
Assert.That(resetTimes, Is.EqualTo(1));

答案 3 :(得分:0)

您可以编写间谍,而不是使用模拟对象。需要更多编码,但测试更容易阅读。

class DependencySpy : IDependency {
    public int ResetCallCount { get; private set; }
    public void Reset() { ResetCallCount++; }
    public void ClearResetCallCount() { ResetCallCount = 0; }
}

测试可以写成

// Setup
var spy = new DependencySpy;
MyClass myclass = new MyClass(spy);
spy.ClearResetCallCount();
// Exercise
myclass.Clear();
// Validate
Assert.AreEqual(1, spy.ResetCallCount);

答案 4 :(得分:-1)

您可以使用反射将私有字段dep设置为模拟对象。然后只需调用Clear方法并测试依赖调用。