【问题标题】:C# Mock concrete class. How?C# 模拟具体类。如何?
【发布时间】:2017-03-08 20:38:20
【问题描述】:

我想模拟一个具体的类,具体来说是 SortedDictionary。

上下文:

我有一个 LocationMapper 类定义如下:

public class LocationMapper
{
  private SortedDictionary<string, Location>() locationMap;
  public LocationMapper()
  {
    this.locationMap = new SortedDictionary<string, Location>();
  }

  public LocationMapper(SortedDictionary<string, Location> locations)
  {
    this.locationMap = locations;
  }

  public Location AddLocation(Location location)
  {
    if(! locationMap.ContainsKey(location.Name))
    {
      locationMap.Add(location.Name, location)
    }
    return locationMap[location.Name];
  }  
}

要对 AddLocation() 进行单元测试,我需要模拟具体类 SortedDictionary。不幸的是,NSubstitute 不允许这样做。

The unit test that I had envisioned to write is below
[Test]
public void AddLocation_ShouldNotAddLocationAgainWhenAlreadyPresent()
{
  var mockLocationMap = ;//TODO
  //Stub mockLocationMap.ContainsKey(Any<String>) to return "true"
  locationMapper = new LocationMapper(mockLocationMap);
  locationMapper.AddLocation(new Location("a"));
  //Verify that mockLocationMap.Add(..) is not called
}

您将如何在 DotNet 中以这种风格编写单元测试?或者你不为已知的约束选择这条路?

非常感谢您的帮助。

【问题讨论】:

  • 为什么要嘲笑它?为什么不直接创建一个实例并传入呢?您可以根据需要完全控制填充它,所以我认为您可以断言结果。如果它是一个接口,那么肯定是模拟的,但是使用具体的字典,我认为不需要它。
  • 您实际测试的是什么?在我看来,您实际上是在测试 SortedDictionary 是否做了它保证做的事情。您希望实现什么价值?
  • @TyCobb,我已经用我的单元测试偏见/偏好作为模板单元测试用例更新了这个问题,供您阅读。
  • 验证 mockLocationMap.Add(..) 未被调用 -- 使用具体实例,添加“a”并断言有 1 个项目。再次添加“a”并断言仍然只有 1 并且尝试添加重复键并没有爆炸

标签: c# unit-testing mocking tdd


【解决方案1】:

另一种方法是使用允许您模拟具体类的单元测试工具,例如我正在使用 Typemock Isolator 并且能够创建您想要进行的测试:

[TestMethod]
public void TestMethod1()
{
    var fakeLocationMap = Isolate.Fake.Instance<SortedDictionary<string, Location>>();

    Isolate.WhenCalled(() => fakeLocationMap.ContainsKey(string.Empty)).WillReturn(true);

    var instance = new LocationMapper(fakeLocationMap);
    var res = instance.AddLocation(new Location("a"));

    Isolate.Verify.WasNotCalled(() => fakeLocationMap.Add(string.Empty, null));
}

【讨论】:

    【解决方案2】:

    你不应该在这里模拟字典。实际上它是LocationMapper 类的一个实现细节。它应该通过封装隐藏。您可以使用其他任何东西来存储位置 - 数组、列表或简单的字典。 LocationMapper 是否满足其要求并不重要。在这种情况下有什么要求?类似的东西

    位置映射器应该能够映射添加到映射器的位置

    目前您的映射器非常无用,它不会对字典行为添加任何内容。您缺少核心 - 映射。我只能假设如何使用这个类。您需要一些公共接口进行映射。并且测试应该看起来像(此处使用 AutoFixture 和 FluentAssertions):

    var mapper = new LocationMapper();
    var location = fixture.Create<Location>();
    mapper.AddLocation(location);
    mapper.Map(location.Name).Should().Be(location);
    

    当此测试通过时,您可以将位置添加到映射器并使用映射器来映射这些位置。

    【讨论】:

    • 我为上下文添加了代码,正如我在帖子中提到的那样。它不是全部:-/
    【解决方案3】:

    您有两个选择:如果您使用 VS Enterprise,请使用 Microsoft Fakes 为您的班级生成 Shim。 (如果你想要样品,请联系我)>

    如果您不使用 VS Enterprise(作为这里的大多数人),您将不得不诉诸反思:

    [Test]
    public void AddLocation_ShouldNotAddLocationAgainWhenAlreadyPresent()
    {
      var locationMapper = new LocationMapper(mockLocationMap);
      locationMapper.AddLocation(new Location("a"));
      var dict = ((SortedDictionary<string, Location>)typeof(LocationMapper).GetField("locationMap", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(locationMapper));
      Assert.AreEqual("a", dict.FirstOrDefault().Name)
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-16
      • 2011-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多