关于c#:将依赖项注入域模型

Injecting a dependency into a domain model

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

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

为此:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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; }
}

这是:

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

或者:

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

或者:

1
2
3
4
5
6
7
8
9
10
public LogItem(string message, string description, string status)
    : this()
{
    Message = message;
    Description = description;
    Status = status;

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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属性授予测试程序集对内部的独占访问权。

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

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

我很清楚,很多人会说,你不应该仅仅为了使域模型具有可测试性而向域模型中添加代码,但我个人认为,当实践表明这一点时,我并不认为这一论点太严重。


尝试以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//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();
}

为了测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
   }
}