Moq在被测试的方法内部创建的对象

本文关键字:内部 创建 对象 方法 Moq 测试 | 更新日期: 2023-09-27 18:14:03

在下面的示例中,我想测试TestMe.DoSomething()函数。

我想模拟此方法中使用的ISomething接口,并使其返回不同的值(取决于特定的单元测试)

在现实生活中,ISomething接口最终调用昂贵的第三方资源——我绝对不想只调用真正的ISomething

结构示例如下:

class TestMe
{
    public void DoSomething()
    {
        ISomething s = SomethingFactory();
        int i = s.Run();
        //do things with i that I want to test
    }
    private ISomething SomethingFactory()
    {
        return new Something();
    }
}
interface ISomething
{
    int Run();
}
class Something : ISomething
{
    public int Run()
    {
        return 1;
    }
}

下面是不能工作的代码:

        var fakeSomething = new Mock<ISomething>();
        var testMe = new TestMe();
        Mock.Get(testMe).Setup(p => p.SomethingFactory()).Returns(fakeSomething.Object);
        testMe.DoSomething();

因为SomethingFactory()private,所以我不能将该方法的返回值设置为我想要的值。

关于如何解决这个问题有什么建议吗?

Moq在被测试的方法内部创建的对象

使工厂成为一个完整的接口/类,并从TestMe中删除SomethingFactory方法。

public interface ISomethingFactory {
  ISomething MakeSomething();
}
public sealed class SomethingFactory {
  public ISomething MakeSomething() {
    return new Something();
  }
}
class TestMe
{
    private readonly ISomethingFactory _somethingFactory;
    public TestMe(ISomethingFactory somethingFactory) {
      _somethingFactory = somethingFactory;
    }
    public void DoSomething()
    {
        ISomething s = _somethingFactory.MakeSomething();
        int i = s.Run();
        //do things with i that I want to test
    }
}

这将允许你模拟issomethingfactory来返回一个模拟的issomething。

虽然我认为你可能会抗议这个解决方案过于激烈的变化,我认为这比创建一个不与成员密封的类要好,这些成员作为虚拟的唯一原因是为了测试。

  1. 你可以注入你的依赖。如果不想破坏所有调用者,可以添加两个构造函数,并使用允许在tests

    中注入fake的构造函数。
    class TestMe
    {
        private readonly ISomething something;
        TestMe() : this(new RealSomething()
        {
        }
        TestMe(ISomething sth)
        {
            something = sth;
        }
        public void DoSomething()
        {
            ISomething s = SomethingFactory();
            int i = s.Run();
            //do things with i that I want to test
        }
        private ISomething SomethingFactory()
        {
            return new Something();
        }
    }
    
  2. 第二种方法是改变

    SomethingFactory
    

    方法来保护虚拟,并在派生类中重写它并使用该类,或者设置

    class TestableTestMe : TestMe
    {
        private readonly ISomething something;
        TestableTestMe(ISomething testSpecific)
        {
            something  = testSpecific;
        }
    
        public void DoSomething()
        {
            ISomething s = SomethingFactory();
            int i = s.Run();
            //do things with i that I want to test
        }
        protected override ISomething SomethingFactory()
        {
            return something;
        }
    }
    

这种技术叫做"提取和覆盖"

将SomethingFactory()更改为受保护的虚拟允许您使用Moq。

public class TestMe 
{
    public void DoSomething()
    {
        ISomething s = SomethingFactory();
        int i = s.Run();
        //do things with i that I want to test
    }
    protected virtual ISomething SomethingFactory()
    {
        return new Something();
    }
}
public interface ISomething
{
    int Run();
}
public class Something : ISomething
{
    public int Run()
    {
        return 1;
    }
}

所以你可以运行这个测试:

        var fakeSomething = new Mock<ISomething>();
        fakeSomething.Setup(p => p.Run()).Returns(2);
        var testMe = new Mock<TestMe>();
        testMe.Protected().Setup<ISomething>("SomethingFactory").Returns(fakeSomething.Object);
        testMe.Object.DoSomething();