如何在 C# 单元测试中手动存根一个函数

How to manually stub out a function in C# unit testing

我正在尝试创建自己的 Mock 函数。我知道最小起订量存在,但我真的很想完成它。

我不知道如何创建 Mock 函数来阻止 User#Foobar 对 Product#Price 的调用,以便对 User#Foobar 进行单元测试:

using System;

public class MyUnitTest
{
    // CODE BEING TESTED
    class Product {
        public string Price() {
            return "I WANT TO STUB THIS OUT, DONT WANT TO SEE THIS STRING";
        }
    }
    class User {
        public string Foobar() {
            Product product = new Product();
            return $"Foobar, Product#price returns: {product.Price()}";
        }    
    }
    //////////////////////
    
    // CODE THAT DOES THE TEST
    public void Mock(string mockedClass, string mockedFunction, string mockedReturn)
    {
        // no idea what to put here something to do with an interface
    }
    
    public void RunTests()
    {
        Mock("Product","Price","MOCKED DATA");
        string expected = new User().Foobar();
        Assert(expected, "Foobar, Product#price returns: MOCKED DATA");
    }
        
    public void Assert(string actual, string expected){
        Console.WriteLine(actual == expected);
    }
    //////////////////////

    // How do I stub out Product to return "MOCKED DATA"?
}
                    
public class Program
{
    public static void Main(){
        new MyUnitTest().RunTests();
    }
}

我知道如何在 Ruby 中执行此操作,但不知道如何在 C# 中执行。

这里有一个 NET fiddle 的问题:

https://dotnetfiddle.net/8Re2jb

这个 Mock 函数应该如何拦截 User#Foobar 对 Product#Price 的调用和return字符串“MOCKED DATA”?

public void Mock(string mockedClass, string mockedFunction, string mockedReturn)
{
}

您应该使用依赖注入来解决这个问题。 User 对象不应该知道 Product 的具体类型。相反,您应该将 Product 封装在 IProduct 接口后面,并将 User 构造函数传递给 IProduct 或某种 IProduct 工厂。

例如:

public interface IProduct
{
    public string Price();
}

public interface IUser
{
    public string FooBar();
}

public class User: IUser
{
    public User(Func<IProduct> productFactory)
    {
        _productFactory = productFactory;
    }

    public string FooBar()
    {
        var product = _productFactory();
        return $"Foobar, Product#price returns: {product.Price()}";
    }

    readonly Func<IProduct> _productFactory;
}

然后您可以创建自己的“模拟”产品(我们应该真正称之为存根):

public class MockProduct : IProduct
{
    public MockProduct(string mockPrice)
    {
        _mockPrice = mockPrice;
    }

    public string Price()
    {
        return _mockPrice;
    }

    readonly string _mockPrice;
}

然后在你的测试中你可以创建 User 并传递给它一个工厂方法来创建一个模拟 IProduct:

Func<IProduct> mockProductFactory = () => new MockProduct("MOCKED DATA");

User user = new User(mockProductFactory);

// Test user now.

尽管这实际上是存根(您已经使用过的术语)而不是模拟的一种形式 - 模拟通常使用模拟框架完成,并使用反射来避免必须手动实现接口中的所有方法。