当代码验证它接收的类型时,如何使用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();
    }
}

绕过这个验证的最好方法是什么?

谢谢。

当代码验证它接收的类型时,如何使用mock ?

首先想到的两个方法是:

  1. Fact添加到允许的类型列表中。
  2. 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());
}