先模拟一个方法而不先嘲笑一个类

我正在使用moq4来模拟我的UnitTests中的东西。 我有一个类说TestClass ,其中一个名为TestMethod的方法是我想要测试的方法。 所以我的问题是我的TestMethod需要检查我的TestClass中的testlist 。 像这样:-

public Class TestClass { public readonly ISomeService _someService; public bool TestProperty { get; private set; } public List testlist { get; private set; } Public TestClass(ISomeService someService) { _someService = someService; } public async Task TestMethod(string sd) { if(TestProperty) // here how can i control this property in UnitTest. return false; testlist.contains(sd); // check in this list but list will be null. } public async Task<List> SetTestListAndProperty() { testlist.Add("2"); testlist.Add("3"); TestProperty = false; } } 

现在在testmethodtests中,我已经模拟了ISomeService并传递给了TestClass构造函数

 var someservicemock = new Mock(); var testclassobj = new TestClass(someservicemock.Object); 

现在我打电话给我的TestMethod

 result = await testclassobj.TestMethod("id"); // it is throwing the exception that testlist is null. 

//当TestProperty为false时,我也想测试TestMethod。 那么如何设置此属性它没有公共setter。 它由其他methd( SetTestListAndProperty )设置。

所以我的问题是我可以在不模仿TestClass的情况下模拟TestClass 测试列表吗? 如果这种方式有误,请告诉我,或者您也知道任何解决方法。 干杯

我认为你的问题归结为:当被测方法依赖于由其他方法设置的内部状态时该怎么办?

这是一个例子:

 class MyCollection : IEnumerable { private List _list = new List(); private Receiver _receiver; public MyCollection() { _receiver = receiver; } public void Add(T item) { _list.Add(item); } public void RemoveAndNotify(T item) { _list.Remove(item); _receiver.Notify(item); } public IEnumerator GetEnumerator() { return _list.GetEnumerator(); } } 

(顺便说一句,调用你的类型TestClassTestPropertytestlist并没有真正的帮助 – 读者不知道这些东西是如何相互关联的。它比令人困惑的Foo,Bar和Baz更令人困惑。使用猫,狗,人等等,具体而有意义的名称。)

  1. 所以,假设您想要对RemoveAndNotify方法进行unit testing。 但是你不能先删除一个项目而不先添加一个项目! 这是否意味着我必须模拟内部_list来插入虚拟数据? 没有!
  2. 另外,如何validation项目是否确实已从内部列表中删除? 我是否必须以某种方式检索内部_list并检查其内容(通过注入,reflection等)? 没有!

我认为你的问题的关键是:unit testing不一定测试一种方法 ! unit testing测试尽可能小的行为单位。

该行为单元可能涉及调用一种或多种方法。 所以这是我如何unit testing与删除项目相关的行为。 我们想要测试两种行为

  1. 我们想要在我们删除它之后测试该集合不再有项目。
  2. 我们想测试一个项目被删除时接收者会收到通知。

 [Fact] public void RemoveAndNotify_RemovesItem() { //Arrange var mockReceiver = ... var collection = new MyCollection(mockReceiver.Object); collection.Add(5); //Act collection.Remove(5); //Assert //internally calls GetEnumerator to verify that the collection no longer contains "5" Assert.AreEqual(Enumerable.Empty(), collection); } [Fact] public void RemoveAndNotify_NotifiesReceiver() { //Arrange var mockReceiver = ... var collection = new MyCollection(mockReceiver.Object); collection.Add(5); //Act collection.Remove(5); //Assert mockReceiver.Verify(rec => rec.Notify(5), Times.Once()); } 

如您所见,第一个unit testing在类( AddRemoveGetEnumerator )上调用了三个方法,以便测试一个行为单元

底线:忘记“测试中的方法”,并考虑“被测试的行为”。 你正在测试行为,而不是方法。

两件事情:

  1. 我不建议你嘲笑一切 。 在收集的情况下,使用真实(非模拟)对象是完全可以的。 模拟列表时的好处在哪里? 通过检查testlist属性,您可以非常轻松地测试所有内容。

  2. (这是附注,但也解决了您的第一条评论。)尝试使您的类型不可变。 您可以找到有关此主题的大量文章。 只是搜索它。 好处是您不必处理NullReferenceException和其他一些场景。 对于您的示例,这意味着您应该在构造函数中初始化testlist

我将创建另一个构造函数,将列表作为参数,以便您可以在测试中创建并填充包含测试数据的列表,然后将其提供给测试中的类。

 public TestClass(ISomeService service, IList list) { _someService = service; testList = list; } 

使用此构造函数,testList不会为null,除非您传入null。

我知道有些人不喜欢添加仅用于测试的方法。 如果您不想公开另一个仅用于测试的构造函数,则可以将新构造函数标记为internal并仅将内部结构暴露给测试程序集。

 internal TestClass(ISomeService service, IList list) { ... } 

在项目的AssemblyInfo.cs

 [assembly: InternalsVisibleTo("your test assembly"); 

编辑:对于属性。 如果您具有不公开setter的属性,则可以将属性标记为virtual并使用Moq的SetupGet方法设置属性返回值。

 public class ClassUnderTest { public virtual string TestProperty { get; private set; } } 

在你的测试中:

 public void SomeTest() { var mock = new Mock(); mock.SetupGet(m => m.TestProperty).Returns("hi"); var result = mock.Object.TestProperty; Assert.That(result, Is.EqualTo("hi"); // should pass }