简体   繁体   English

Moq单元测试C#类库项目的最佳方法

[英]Best way to moq unit test c# class library project

SalaryManager class has a method named DoCalculation, which calls a GetSum method which is implemented using the factory pattern. SalaryManager类具有一个名为DoCalculation的方法,该方法调用使用工厂模式实现的GetSum方法。 The DoCalculation method also does some other operation other than calling the GetSum method. 除了调用GetSum方法外,DoCalculation方法还执行其他一些操作。 I want to unit test the DoCalculation method by mocking the call to GetSum(). 我想通过模拟对GetSum()的调用来对DoCalculation方法进行单元测试。 Can some one suggest the best way to implement it in Moq mocking. 有人可以提出在Moq模拟中实现它的最佳方法吗? Below is the sample code, 下面是示例代码,

interface ICalc
{
int GetSum(int a, int b);
}

    class NormalCalc : ICalc
    {
        public int GetSum(int a,int b)
        {
            return a + b;
        }
    }
    class SumFactory
    {
        public static ICalc GetSumObject(int option)
        {
            if (option == 1)
                return new NormalCalc();
            return null;
        }
    }
    class SalaryManager
    {
        private static ICalc CalcRef = SumFactory.GetSumObject(1);

        public int DoCalculation(int a, int b)
        {
            int Sum=CalcRef.GetSum(a, b);
            //Perform some other operation
            //
            //
        }
    }

Don't use static factory method. 不要使用静态工厂方法。 Convert factory class into inject-able service/interface and inject it into system under test. 将工厂类转换为可注入的服务/接口,并将其注入被测系统。

public interface ISumFactory {
    ICalc GetSumObject(int option);
}

public class SumFactory : ISumFactory {
    public  ICalc GetSumObject(int option) {
        if (option == 1)
            return new NormalCalc();
        return null;
    }
}
public class SalaryManager {
    private ICalc CalcRef;
    public SalaryManager(ISumFactory factory) {
        CalcRef = factory.GetSumObject(1);
    }

    public int DoCalculation(int a, int b) {
        int Sum = CalcRef.GetSum(a, b);
        //Perform some other operation
        //
        //
        //...;
    }
}

Then mock the dependencies for test and verify expectations. 然后模拟依赖性进行测试并验证期望。

[TestClass]
public class MyTestClass {
    [TestMethod]
    public void MyTestMethod() {
        //Arrange
        var calcMock = new Mock<ICalc>();
        calcMock.Setup(m => m.GetSum(It.IsAny<int>(), It.IsAny<int>()))
            .Returns((int a, int b) => a + b)
            .Verifiable();

        var factoryMock = new Mock<ISumFactory>();
        factoryMock.Setup(m => m.GetSumObject(1)).Returns(calcMock.Object)
        .Verifiable();

        var sut = new SalaryManager(factoryMock.Object);

        //Act
        var result = sut.DoCalculation(1, 1);

        //Assert
        //...
        factoryMock.Verify();
        calcMock.Verify();
    }
}

In order to mock the GetSum method you will need to inject ICalc dependency to SalaryManager class somehow. 为了模拟GetSum方法,您将需要以某种方式将ICalc依赖项注入SalaryManager类。

Although SalaryManager creates ICalc using a factory and does not by itself it isn't good enough, the factory method itself is static method that returns a concrete class and you can't change it to return mocked class instead becuase it is a static method, hence according to this design even your unit tests will have to use NormalCalc as Icalc implementation. 尽管SalaryManager使用工厂创建ICalc并不能令人满意,但工厂方法本身是返回具体类的静态方法,您不能将其更改为返回模拟类,而是因为它是静态方法,因此,根据这种设计,即使您的单元测试也必须使用NormalCalc作为Icalc实现。

You have basically 2 options: 您基本上有2个选择:

  1. Inject ICalc directly to SalaryManager via it's constructor: 通过其构造函数将ICalc直接注入SalaryManager:

     public class SalaryManager { private readonly ICalc _calc; public SalaryManager(ICalc clac) { _calc = calc; } public int DoCalculation(int a, int b) { int Sum = _calc.GetSum(a, b); //... } } 

    Now it is easy to inject a mocked instance of ICalc to your class, just create the a mock of ICalc using moq and pass it to the calss via the constructor. 现在可以很容易地将模拟的ICalc实例注入您的类,只需使用moq创建一个ICalc的模拟并将其通过构造函数传递给calss。

  2. Anotehr option if you still want to use a factory(which seems pretty useless in your case according to what it does, as a side note I tend to use factories while working with DI only when the class depends on an IDisposable object that I don't want to keep alive for the class's entire life time) is to change it from static method to a concrete factory that will implememnt an interface : Anotehr选项,如果您仍想使用工厂(根据其用途,这在您的情况下似乎毫无用处,作为一个侧面说明,仅当类依赖于我不依赖于IDisposable对象的情况下,我倾向于在使用DI时使用工厂想要在类的整个生命周期中保持生命)是将其从静态方法更改为将实现接口的具体工厂:

     public interface ISumFactory { ICalc GetCalc(int option); } public SumFactory : ISumFactory { public ICalc GetCalc(int option) { if (option == 1) return new NormalCalc(); return null; } } 

    Now you should inject the factory interafce to SalaryManager class via it's constructor and use it when you need it: 现在,您应该通过工厂构造函数将工厂接口注入到SalaryManager类中,并在需要时使用它:

     public class SalaryManager { private readonly ICalcFacotry _calcFactory; public SalaryManager(ICalcFacotry clacFacotry) { _calcFactory = clacFacotry; } public int DoCalculation(int a, int b) { ICalc calc = _calcFactory.GetCalc(1); int Sum = calc.GetSum(a, b); //... } } 

    Now in your unit test you can create ICalcFacotry mock and pass it to your class, you should setup the mocked facotry to return an ICalc mock when using the facotry method with 1 as the option. 现在,在单元测试中,您可以创建ICalcFacotry模拟并将其传递给您的类,当使用facotry方法(选项为1)时,应设置模拟的facotry以返回ICalc模拟。

You can unit test this method without changing your source code. 您可以对该方法进行单元测试,而无需更改源代码。
By using Typmock Isolator you will be able to mock a future instance of a class before it's even created so when you mock a future instance of Iclac it will mock all the classes that implements this interface. 通过使用Typmock Isolator,您将能够在类的未来实例创建之前对其进行模拟,因此,当您模拟Iclac的未来实例时,它将模拟实现该接口的所有类。

for example: 例如:

[TestMethod]
public void TestMethod()
{
    //mock the future instances of Iclac 
    //and when the next NormalClac will be created it will be mocked as well
    var fakeIclac = Isolate.Fake.NextInstance<ICalc>();

    //setting the behavior of GetSum
    Isolate.WhenCalled(() => fakeIclac.GetSum(0, 0)).WillReturn(5);

    var result = new SalaryManager().DoCalculation(0, 0);

    Assert.AreEqual(5, result);
} 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM