我正在使用moq4来模拟 UnitTests 中的内容。我有一个课程说 TestClass ,其中一个名为 TestMethod 的方法是我想要测试的方法。 所以我的问题是我的 TestMethod 需要检查我的TestClass中的 testlist 。像这样: -
public Class TestClass
{
public readonly ISomeService _someService;
public bool TestProperty { get; private set; }
public List<string> testlist { get; private set; }
Public TestClass(ISomeService someService)
{
_someService = someService;
}
public async Task<bool> 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<string>> SetTestListAndProperty()
{
testlist.Add("2");
testlist.Add("3");
TestProperty = false;
}
}
现在在 testmethodtests中,我已经模拟了ISomeService并传递给了TestClass构造函数。
var someservicemock = new Mock<ISomeService>();
var testclassobj = new TestClass(someservicemock.Object);
现在我打电话给我的TestMethod
result = await testclassobj.TestMethod("id"); // it is throwing the exception that testlist is null.
//当TestProperty为false时,我也想测试TestMethod。那么我怎样才能设置它没有公共设置器的属性。它由其他方法( SetTestListAndProperty )设置。
所以我的问题是我可以在不模拟TestClass的情况下模拟TestClass testlist 吗? 如果这种方式有误,请告诉我,或者您也知道任何解决方法。 欢呼声
答案 0 :(得分:1)
两件事:
我不建议您模拟所有。在收集的情况下,使用真实(非模拟)对象是完全可以的。模拟列表时的好处在哪里?您可以通过检查testlist
属性来轻松测试所有内容。
(这是旁注,但也会解决您的第一条评论。)尝试使您的类型不可变。您可以找到有关此主题的大量文章。只是搜索它。好处是您不必处理NullReferenceException
和其他一些场景。对于您的示例,这意味着您应该在构造函数中初始化testlist
。
答案 1 :(得分:1)
我认为你的问题归结为:当测试中的方法取决于由其他方法设置的内部状态时该怎么办?
以下是一个例子:
class MyCollection<T> : IEnumerable<T>
{
private List<T> _list = new List<T>();
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<T> GetEnumerator()
{
return _list.GetEnumerator();
}
}
(顺便说一句,调用您的类型TestClass
,TestProperty
和testlist
并没有真正帮助 - 读者不知道这些事情是如何相互关联的。它是令人困惑的是Foo,Bar和Baz。用猫,狗,人等来代替具体而有意义的名字。)
RemoveAndNotify
方法进行单元测试。但是,如果不首先添加项目,则无法删除项目!
这是否意味着我必须模拟内部_list
来插入虚拟数据? NO!我认为你的问题的症结在于:单元测试不一定要测试一种方法! 单元测试测试行为的最小单位。
该行为单元可能涉及调用一个或更多方法。所以这是我如何单元测试与删除项目相关的行为。 我们想要测试两个行为。
[Fact]
public void RemoveAndNotify_RemovesItem()
{
//Arrange
var mockReceiver = ...
var collection = new MyCollection<int>(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<int>(), collection);
}
[Fact]
public void RemoveAndNotify_NotifiesReceiver()
{
//Arrange
var mockReceiver = ...
var collection = new MyCollection<int>(mockReceiver.Object);
collection.Add(5);
//Act
collection.Remove(5);
//Assert
mockReceiver.Verify(rec => rec.Notify(5), Times.Once());
}
正如您所看到的,第一个单元测试在类(Add
,Remove
和GetEnumerator
)上调用了三个方法,以便测试一个行为单位。
底线:忘记“测试中的方法”,并考虑“测试中的行为”。你正在测试行为,而不是方法。
答案 2 :(得分:0)
我会创建另一个构造函数,它将列表作为参数,这样您就可以在测试中创建并填充包含测试数据的列表,然后将其提供给被测试的类。
public TestClass(ISomeService service, IList<string> list) {
_someService = service;
testList = list;
}
使用此构造函数,testList不会为null,除非您在。中传递null。
我知道有些人不想添加仅用于测试的方法。如果您不想公开另一个仅用于测试的构造函数,则可以标记新的构造函数internal
并仅将内部结构暴露给您的测试程序集。
internal TestClass(ISomeService service, IList<string> list) {
...
}
在项目的AssemblyInfo.cs
中:
[assembly: InternalsVisibleTo("your test assembly");
编辑:对于属性。如果您有不公开setter的属性,则可以标记属性virtual
并使用Moq&lt; SetupGet
方法设置属性返回值。
public class ClassUnderTest
{
public virtual string TestProperty { get; private set; }
}
在你的测试中:
public void SomeTest() {
var mock = new Mock<ClassUnderTest>();
mock.SetupGet(m => m.TestProperty).Returns("hi");
var result = mock.Object.TestProperty;
Assert.That(result, Is.EqualTo("hi"); // should pass
}