当代码验证它接收的类型时,如何使用mock ?
本文关键字:何使用 mock 类型 验证 代码 | 更新日期: 2023-09-27 18:09:57
我想测试以下代码:
public IEnumerable<KeyValuePair<Fact, Exception>> ValidateAll()
{
//...do something
var invalidFacts = GetInvalidFacts();
//...do something
return duplicateFacts.Concat(invalidFacts);
}
private IEnumerable<KeyValuePair<Fact, Exception>> GetInvalidFacts()
{
var invalidFacts = Facts.Select(fact =>
{
try
{
fact.Validate();
return new KeyValuePair<Fact, Exception>(fact, null);
}
catch (FormatException e)
{
return new KeyValuePair<Fact, Exception>(fact, e);
}
catch (Exception e)
{
return new KeyValuePair<Fact, Exception>(fact, e);
}
}).Where(kv => kv.Value != null).ToList();
return invalidFacts;
}
基本上,测试的目标是验证存在于"Facts"IEnumerable中的所有对象将调用它们的Validate方法。因为我对测试这些对象中的代码不感兴趣,已经有很多测试在做这件事了,所以我想注入一个假事实列表。我使用最小起订量来创建假货。
所以我的单元测试是这样的:[TestMethod]
public void ValidateAll_ValidateMethodIsInvokedOnAllFacts_WhenCalled()
{
var anyFactOne = new Mock<Fact>(); //Fact is an abstract class.
anyFactOne.Setup(f => f.Validate());
var dataWarehouseFacts = new DataWarehouseFacts { Facts = new Fact[] { anyFactOne.Object, FactGenerationHelper.GenerateRandomFact<SourceDetails>() } };
dataWarehouseFacts.ValidateAll();
}
现在我得到了一个异常,因为代码实际上正在验证可以注入到DataWarehouseFacts类的事实类型,如下所示:
public IEnumerable<Fact> Facts
{
get
{
.....
}
set
{
var allowedTypes = new []
{
typeof(ProductUnitFact),
typeof(FailureFact),
typeof(DefectFact),
typeof(ProcessRunFact),
typeof(CustomerFact),
typeof(ProductUnitReturnFact),
typeof(ShipmentFact),
typeof(EventFact),
typeof(ComponentUnitFact),
typeof(SourceDetails)
};
if(!value.All(rootFact => allowedTypes.Contains(rootFact.GetType())))
throw new Exception ("DataWarehouseFacts can only be set with root facts");
ProductUnitFacts = value.OfType<ProductUnitFact>().ToList();
FailureFacts = value.OfType<FailureFact>().ToList();
DefectFacts = value.OfType<DefectFact>().ToList();
ProcessRunFacts = value.OfType<ProcessRunFact>().ToList();
CustomerFacts = value.OfType<CustomerFact>().ToList();
ProductUnitReturnFacts = value.OfType<ProductUnitReturnFact>().ToList();
ShipmentFacts = value.OfType<ShipmentFact>().ToList();
EventFacts = value.OfType<EventFact>().ToList();
ComponentUnitFacts = value.OfType<ComponentUnitFact>().ToList();
SourceDetails = value.OfType<SourceDetails>().Single();
}
}
绕过这个验证的最好方法是什么?
谢谢。
首先想到的两个方法是:
- 将
Fact
添加到允许的类型列表中。 - Moq您允许的事实类型之一,而不是基础
Fact
类本身。(我假定你的Validate()
方法是可重写的。)
另一个稍微复杂一点的选项是在测试时注入允许的类型列表,假设您可以控制DataWarehouseFacts
类。它可能看起来像这样:
class DWF
{
static IEnumerable<Fact> defaultAllowedFacts = new Fact[] { ... }
IEnumerable<Fact> allowedFacts;
public DWF() : this(defaultAllowedFacts) { ... }
internal DWF(IEnumerable<Fact> allowed)
{
// for testing only, perhaps
this.allowedFacts = allowed;
}
...
}
然后删除var allowedTypes = new []
位,使用this.allowedFacts
代替。
我会利用Type。IsAssignableFrom
。而不是说
allowedTypes.Contains(v.GetType())
我想说
allowedTypes.Any(t => t.IsAssignableFrom(v.GetType()))
这样就可以传递适当的子类以及精确匹配的类型。也许,也许,这就是你对类型列表本身所追求的?
首先我要感谢ladenedge(我给他的回答打了+1)和她的回答。尽管这不是我想要的,但它们是值得记住的有趣的想法。
我不能仅仅将Fact类添加到允许的类型列表中,因为这将为许多不应该被允许的类打开大门;大约有30个类继承自它。
所以我最终做的是在他们自己的方法中从Facts属性的set部分提取代码,并将其中一个设置为protected virtual,如下所示:
public IEnumerable<Fact> Facts
{
get
{
...
}
set
{
ValidateReceived(value);
ExtractFactTypesFrom(value.ToList());
}
}
protected virtual void ValidateReceived(IEnumerable<Fact> factTypes)
{
if (factTypes == null) throw new ArgumentNullException("factTypes");
var allowedTypes = GetAllowedFactTypes();
if (!factTypes.All(rootFact => allowedTypes.Contains(rootFact.GetType()))) throw new Exception("DataWarehouseFacts can only be set with root facts");
}
private IEnumerable<Type> GetAllowedFactTypes()
{
var allowedTypes = new[]
{
typeof (ProductUnitFact),
typeof (SequenceRunFact),
typeof (FailureFact),
typeof (DefectFact),
typeof (ProcessRunFact),
typeof (CustomerFact),
typeof (ProductUnitReturnFact),
typeof (ShipmentFact),
typeof (EventFact),
typeof (ComponentUnitFact),
typeof (SourceDetails)
};
return allowedTypes;
}
private void ExtractFactTypesFrom(List<Fact> value)
{
ProductUnitFacts = value.OfType<ProductUnitFact>().ToList();
FailureFacts = value.OfType<FailureFact>().ToList();
DefectFacts = value.OfType<DefectFact>().ToList();
ProcessRunFacts = value.OfType<ProcessRunFact>().ToList();
SequenceRunFacts = value.OfType<SequenceRunFact>().ToList();
CustomerFacts = value.OfType<CustomerFact>().ToList();
ProductUnitReturnFacts = value.OfType<ProductUnitReturnFact>().ToList();
ShipmentFacts = value.OfType<ShipmentFact>().ToList();
EventFacts = value.OfType<EventFact>().ToList();
ComponentUnitFacts = value.OfType<ComponentUnitFact>().ToList();
SourceDetails = value.OfType<SourceDetails>().Single();
}
这样我就可以创建一个DataWarehouseFactsForTest并覆盖validaterreceived方法,这样它就不会做任何事情了:
public class DataWarehouseFactsForTests : DataWarehouseFacts
{
protected override void ValidateReceived(IEnumerable<Fact> factTypes)
{}
}
这样我就可以使用Moq来创建事实并验证私有GetInvalidFacts方法中的代码。例如:
[TestMethod]
public void ValidateAll_ReturnsADictionaryWithAFormatException_WhenOneOfTheFactsValidationThrowsAFormatException()
{
var anyFactOne = new Mock<ProductUnitFact>();
var anyFactTwo = new Mock<SequenceRunFact>();
var anyFactThree = new Mock<SourceDetails>();
anyFactOne.Setup(f => f.Validate()).Throws(new FormatException());
var dataWarehouseFacts = new DataWarehouseFactsForTests { Facts = new Fact[] { anyFactOne.Object, anyFactTwo.Object, anyFactThree.Object } };
var result = dataWarehouseFacts.ValidateAll().ToList();
anyFactOne.Verify(f => f.Validate(), Times.Exactly(1));
anyFactTwo.Verify(f => f.Validate(), Times.Exactly(1));
anyFactThree.Verify(f => f.Validate(), Times.Exactly(1));
Assert.AreEqual(1, result.Count());
Assert.AreEqual(typeof(FormatException), result.First().Value.GetType());
}