【问题标题】:Can NSubstitute mock a return value based on an interface arg rather than concrete type?NSubstitute 可以基于接口 arg 而不是具体类型模拟返回值吗?
【发布时间】:2017-04-12 03:56:21
【问题描述】:

我有一个实体和一个验证器:

public class Customer : IEntity { /* ... */ }
public class CustomerValidator : IValidator<Customer> { /* ... */ }

我想模拟验证器工厂:

public interface IValidatorFactory
{
  IValidator<TEntity> create<TEntity>(TEntity entity) where TEntity : class, IEntity;
}

我的模拟:

var mockFactory = Substitute.For<IValidatorFactory>();
mockFactory.create(Arg.Any<Customer>()).Returns(m => new CustomerValidator());

当参数是 Customer 时,此方法有效。

但我正在测试的代码通过了IEntity,所以模拟不起作用。

我可以重写模拟来处理输入到接口而不是具体类的参数吗?

【问题讨论】:

  • 我尝试了以下变体,但无法编译 mock.create(Arg.Is&lt;IEntity&gt;(a =&gt; a is Customer)).Returns(m =&gt; new CustomerValidator()); 错误为 Cannot convert lambda expression to type 'IValidator&lt;IEntity&gt;' because it is not a delegate type
  • 不可能同时拥有协变和逆变。我相信测试不应该强加架构决策。仍然可以编写可测试的代码,而无需为测试调整代码。
  • @grokky - 你能分享正在测试的代码/方法吗?我认为如果我们有更多的背景信息会有所帮助。
  • @grokky 我正在尝试弄清楚您的真实代码如何使用CustomerValidator 作为IValidator&lt;IEntity&gt;?无论您的真实代码在做什么,您都需要在测试中采用类似的方法。就问题的 NSub 部分而言,我认为重点是确保正确的通用实例被存根,create&lt;IEntity&gt;() 而不是create&lt;Customer&gt;(如亚历山大的回答所示)。剩下的就是弄清楚你希望这些类型如何组合在一起。 :)
  • @DavidTchepak 谢谢你,我已经用你和亚历山大的建议解决了这个问题。问题不是 NSubstitute,而是 SUT 的通用设计。类型如何“挂在一起”是真正的问题! :)

标签: .net unit-testing mocking nsubstitute


【解决方案1】:

您正在处理泛型,默认情况下它们不会“保留”类型参数继承。泛型类型参数默认是不变的。这意味着您不能将IValidator&lt;Customer&gt; 的实例返回为IValidator&lt;IEntity&gt;,即使Customer 派生自IEntity。您需要明确指定逆变关系,使您能够使用比最初实例化的IValidator&lt;Customer&gt; 更通用的类型IValidator&lt;IEntity&gt;。只需用out 关键字标记类型参数即可:

public interface IValidator<out T> where T : IEntity { }

你可以在"Covariance and Contravariance in Generics"阅读到

这是完整的示例:

public interface IValidatorFactory
{
    IValidator<TEntity> create<TEntity>(TEntity entity) where TEntity : class, IEntity;
}

public interface IEntity { }
public interface IValidator<out T> { }
public class Customer : IEntity { }
public class CustomerValidator : IValidator<Customer> { }

[Test]
public void SO_43360005()
{
    var sub = Substitute.For<IValidatorFactory>();
    sub.create(Arg.Is<IEntity>(x => x is Customer)).Returns(_ => new CustomerValidator());
}

【讨论】:

  • 感谢您的协助。更改为逆变解决了一个问题,但会产生另一个问题。我已对问题添加了更新,您介意看一下吗?
  • @grokky 是的,暗示逆变是一个愚蠢的想法。您很可能有 IValidator 接受 IEntity 作为参数。但至少它解释了你遇到的问题。
  • 一点也不傻,你教会了我很多。不知道如何解决它,但至少我理解这个问题。 (感谢您在图书馆所做的所有工作!)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-10
  • 1970-01-01
相关资源
最近更新 更多