【问题标题】:Injecting a dependency into a domain model [closed]将依赖项注入域模型[关闭]
【发布时间】:2013-12-20 19:57:35
【问题描述】:

我有一个域模型,它有一个 OccurredOn 字段,应该初始化为当前日期。通常我会在构造函数中将它设置为 DateTime.Now。

现在我正在尝试创建一个IClock 接口,以便我可以模拟DateTime.Now 进行单元测试。但是,现在我会将IClock 依赖项注入到我的域模型中吗?我应该手动将实例化的Clock 实例传递给构造函数吗?我应该使用我的 DI 容器来解决构造函数中的依赖关系吗?我应该创建一个工厂类来给我一个初始化的对象吗?我离基地很远吗?

为此:

public class LogItem : KeyedEntityBase
{
    public LogItem(string message, string description, string status)
        : this()
    {
        Message = message;
        Description = description;
        Status = status;
    }
    private LogItem() { }

    public DateTime OccurredOn { get; set; }
    public string Message { get; set; }
    public string Description { get; set; }
    public string Status { get; set; }
}

这个:

var newItem = new LogItem("message", "desc", "status", new Clock());

或者这个:

var newItem = LogItemFactory.CreateNewLogItem("message", "desc", "status");

或者这个:

public LogItem(string message, string description, string status)
    : this()
{
    Message = message;
    Description = description;
    Status = status;

    var clock = ObjectFactory.GetInstance<IClock>();
    OccurredOn = clock.Now();
}

【问题讨论】:

标签: c# unit-testing dependency-injection mocking


【解决方案1】:

最简单的方法是在 LogItem 中声明一个额外的 c'tor,它只能用于测试,如下所示:

public class LogItem : KeyedEntityBase
{
    public LogItem(string message, string description, string status)
        : this()
    {
        Message = message;
        Description = description;
        Status = status;
    }
    private LogItem() { }

    internal LogItem(string message, string description, string status, DateTime dateTme)
        : this(message, description, status)
    {
        OccuredOn = dateTime;
    }

    public DateTime OccurredOn { get; set; }
    public string Message { get; set; }
    public string Description { get; set; }
    public string Status { get; set; }
}

然后使用 InternalVisibleTo 属性授予您的测试程序集对内部的独占访问权限。

然后在你的测试中(并且只有在那里)你可以写:

var newItem = new LogItem("message", "desc", "status", <date-whatever>);

我很清楚,很多人会说您不应该仅仅为了使其可测试而将代码添加到您的域模型中,但是当实践表明并非如此时,我个人并不认为这个论点过于严肃。

【讨论】:

  • 然后你会在我的公共 ctor 中建议 OccurredOn = DateTime.Now
  • 我想是的。这就是你生产所需要的,对吧?
  • 是的。我不知道“InternalVisibleTo”属性。会读起来。谢谢!
【解决方案2】:

这样试试:

//your object
public class LogItem {
   public LogItem(string message, string description, string status) {
      Message = message;
      Description = description;
      Status = status;
   }
   public System.DateTime OccurredOn { get; set; }
   public string Message { get; set; }
   public string Description { get; set; }
   public string Status { get; set; }
}

public interface ILogFactory {
   LogItem CreateNew(string message, string description, string status);
}

public class LogFactory: ILogFactory {
   private readonly IClock clock;
   public LogFactory(IClock clock) {
      Requires.IsNotNull(clock, "clock");
      this.clock = clock;
   }
   public LogItem CreateNew(string message, string description, string status) {
      var item = new LogItem (message, description, status);
      item.OccurredOn = clock.Now();
      return item;
   }
}

public interface IClock {
   System.DateTime Now();
}

为了测试:

using NSubstitute; // see http://nsubstitute.github.io/
using NUF = NUnit.Framework;
[NUF.TestFixture]
public class LogFactoryTest {
   [NUF.Test]public void CreateNew_should_call_now() {
      var clock = Substitute.For<IClock>();
      var fac = new LogFactory(clock);
      fac.CreateNew("a", "b", "c");
      clock.Received().Now();
   }

   [NUF.Test]public void CreateNew_should_set_correct_value() {
      var clock = Substitute.For<IClock>();
      var now = new System.DateTime(2014, 1, 15, 18, 0, 0);
      clock.Now().Returns(now);

      var fac = new LogFactory(clock);
      LogItem log = fac.CreateNew("a", "b", "c");

      NUF.Assert.That(log.OccurredOn, NUF.Is.EqualTo(now));
   }
}

【讨论】:

    猜你喜欢
    • 2014-07-26
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 2011-03-22
    • 1970-01-01
    • 2016-07-24
    • 1970-01-01
    相关资源
    最近更新 更多