如何在 C# 单元测试中伪造内置方法的 return?

How do I fake the return of a built-in method in C# unit test?

我有一个 public 静态方法,它使用随机对象 class 来生成一个整数。然后将该整数作为列表的索引。我想在控制随机对象的 Next 方法 returns.

的同时测试该方法的逻辑
public class RandomGenerator
  {

    // a static method which produces a random selection from a List
    public static string GetRandomListMember(List<string> anyStringList)
    {
      Random rand = new Random();
      int randomInt = rand.Next(anyStringList.Count);
      return anyStringList[randomInt];
    }
  }

来自我的 MSTest 文件:

[TestClass]
  public class RandomGeneratorSpec
  {
    [TestMethod]
    public void GetRandomListMember_ListOfStrings()
    {
      List<string> strings = new List<string> { "red", "yellow", "green" }; 

      Mock<Random> mockRandom = new Mock<Random>();
      mockRandom.Setup(rand => rand.Next(strings.Count)).Returns(() => 2); // 2!

      string result = RandomGenerator.GetRandomListMember(strings);

      Assert.AreEqual("green", result);
    }
  }

我希望每次在这个测试中运行时 mockRandom.Next 到 return 2。显然,这不是这样做的方法。我一直在搜索示例,但它们似乎都在对象上使用方法并使用实例方法而不是静态方法。

如何在单元测试使用它的方法时控制内置方法 return 的内容?

您可以使用您需要从 System.Random class.

重新定义的任何方法在本地创建自己的 Random class
class Program
{
    static void Main(string[] args)
    {
        var rand = new Random();
        int next = rand.Next(); // uses my local Random class.
    }
}

class Random
{
    public int Next() => 2;
}

考虑重构以实现更易于维护的设计

public class RandomGenerator {
    private readonly Random random;

    public RandomGenerator(Random random = default(Random)) {
        this.random = random ?? new Random();
    }

    public string GetRandomListMember(List<string> anyStringList) {
        int randomInt = random.Next(anyStringList.Count);
        return anyStringList[randomInt];
    }
}

这允许更灵活地使用和测试主题 class

[TestClass]
public class RandomGeneratorSpec {
    [TestMethod]
    public void GetRandomListMember_ListOfStrings() {
        //Arrange
        List<string> strings = new List<string> { "red", "yellow", "green" };
        string expected = "green";
        Mock<Random> mockRandom = new Mock<Random>();
        mockRandom.Setup(rand => rand.Next(strings.Count)).Returns(() => 2); // 2!
        var subject = new RandomGenerator(mockRandom.Object);

        //Act
        string actual = subject.GetRandomListMember(strings);

        //Assert
        Assert.AreEqual(expected, actual);
    }
}

理想情况下,如果这个生成器要用作服务,它应该有自己的抽象

public interface IRandomGenerator {
    string GetRandomListMember(List<string> anyStringList);
}

public class RandomGenerator : IRandomGenerator {
    //...omitted for brevity
}

这样它就可以在需要的地方显式注入,而不是静态地用作可以被认为是代码味道的紧密耦合的依赖项。

我在评论中的建议示例:

[TestClass]
public class RandomGeneratorSpec
{
    [TestMethod]
    public void GetRandomListMember_ListOfStrings()
    {
        List<string> strings = new List<string> { "red", "yellow", "green" }; 

        string result = RandomGenerator.GetRandomListMember(strings);

        Assert.IsTrue(strings.Contains(result));
    }
}