使用 MEF 导入的 C# 单元测试方法

C# Unit Test method with MEF Imports

嗨!

我有一个 class 看起来像这样:

public class ItemCollector {
  public IKnownAuthority _DefaultAuthority = new DefaultAuthority();

  [ImportMany(typeof(IKnownAuthority))]
  private IEnumerable<Lazy<IKnownAuthority, IKnownAuthorityMetadata>> _KnownAuthorities { get; set; }

  public ItemCollector() {
    var catalog = new AggregateCatalog();
    catalog.Catalogs.Add(new DirectoryCatalog(
                Path.Combine(Environment.CurrentDirectory, AUTHORITY_FOLDER)));

    var container = new CompositionContainer(catalog);
    container.ComposeParts(this);
  }

  public IEnumerable<Items> GetItems(string Auth) {
    var Authority = _KnownAuthorities.FirstOrDefault(x => 
                            x.Metadata.AuthorityName.ToLowerInvariant() == 
                            Auth.ToLowerInvariant());

    if (Authority == null)
      return _DefaultAuthority.GetItems(Address);

    return Authority.Value.GetItems(Address);
  }
}

如您所见,有一个方法 (GetItems),我想对该方法进行单元测试。该方法只是尝试在 MEF 容器中查找一个值,然后 return 将找到的项目的结果或默认值组合在一起。 (所以我不想测试 MEF 的东西 [我认为其他人已经这样做了 :)] 我只想测试该方法是否正确 return 是关于参数的默认项或导入项的值)

这是我的问题。我可以创建一个测试来检查默认值,但假设目录(对于 DirectoryCatalog)为空,我无法为特定 "Authority" 创建一个带有存根的测试。那么有没有一种方法可以从测试方法"inject"一个Stub[IKnownAuthority]到所有已经组成的目录?

我试过这样的方法:

[TestMethod]
public void GetItems_Ok()
{
  ItemsCollector collector = new ItemsCollector();
  collector._DefaultAuthority = new StubDefaultAuthority_01();

  CompositionContainer container = new CompositionContainer();
  container.ComposeExportedValue<IKnownAuthority>(new StubAuthority_02());
  container.ComposeParts(collector);

  var list = collector.GetItems("testing.test"));      
  Assert.IsTrue(list.Count() > 0);
}

[Export(typeof(IKnownAuthority))]
[ExportMetadata("AuthorityName", "testing.test")]
public class StubAuthority_02 : IKnownAuthority
{
  public IEnumerable<Items> GetItems(string Auth) {
    return new List<Items>() { new Items() };
  }
}

StubAuthority_02 找不到通往 _KnownAuthorities 集合的路径。首先我认为原因是我在构造函数中组合了部分,然后在测试方法中再次组合(没有设置属性的重组)。但是如果我删除

container.ComposeParts(this);
来自 ItemCollector 构造函数的

行 StubAuthority_02 无论如何都不存在。我确定这只是理解上的问题,但我无法弄清楚...

无法按照您尝试的方式合并两个容器。每当您调用 ItemsCollector 的构造函数时,您都会创建一个 CompositionContainer 并调用 Compose/ComposeParts。这会导致 MEF 根据您提供的目录构建其内部结构,以便能够在您请求时为您提供正确的值。但是由于您在单元测试中说过您的 DirectoryCatalog 是空的,MEF 将找不到任何导出来填充您的导入。

在单元测试的情况下,第二个 CompositionContainer 您将提供一个已经构建的 ItemsCollector 实例。这将导致 MEF 不为其解析任何导入,而只是将其视为给定。

因此,如果您想坚持将 CompositionContainer 封装在 ItemsCollector 中的结构,正确进行单元测试的唯一方法是在两种情况下都填充该目录。

另一种选择是将 CompositionContainer 移出您的 ItemsCollector 并将目录提供给您的容器。对于您的应用程序代码和单元测试代码,目录会有所不同。

如果您不想将 CompositionContainer 移到您的 class 之外,您也可以将目录传递给您的 ItemsCollector:

... // Your ItemsCollector constructor
public ItemsCollector(ComposablePartCatalog catalog)
{
    var container = new CompositionContainer(catalog);
    Container.Compose(this);
}

如果您使用应用程序,您可以像这样创建收集器实例:

// application code
var directoryCatalog = new DirectoryCatalog(
        Path.Combine(Environment.CurrentDirectory, AUTHORITY_FOLDER));
var collector = new ItemsCollector(directoryCatalog);

如果要进行单元测试,您可以这样做:

// unit test code
var catalog = new TypeCatalog(new Type[] {typeof(StubAuthority_02)});
var collector = new ItemsCollector(catalog);

如果您不喜欢自己类型 public API 中的 MEF 类型,请创建自己的类型来包装目录。

希望这对您有所帮助 - 如果您认为代码可以更好地解释它,我们将提供代码。