• 隔离框架Moq


    隔离框架,一个能够在运行时新建和配置伪对象的可重用的类库,它让开发者不用为了伪对象而编写重复的代码。隔离框架可以替我们动态的生成需要的伪对象,节省很多精力。
    Moq的机制是对于Mock的接口,生成一个实现类,这个实现类里的方法都没有具体的实现,而是根据用户的设置直接返回。
    Moq的限制

    • 必须是可以被继承的对象才能被mock;
    • 必须是可以被重写的方法才能被mock。

    原方法

    public static bool IsValidHtml(ITextReader textReader)
    {
        var htmlString = textReader.Read();
        return htmlString.Contains("html");
    }
    

    提取依赖类的接口

    public interface ITextReader
    {
        void BeginRead();
        string Read();
        void EndRead();
    }
    

    使用Moq来隔离ITextReader。

    Setup+Return

    [TestMethod]
    public void IsValidHtml_EmptyString_returnFalse()
    {
        //Arrange
        //新建一个ITextReader的Mock对象,其Object属性即为我们需要的伪对象
        var textReaderMock = new Mock<ITextReader>();
     
     
        //对伪对象的方法进行mock
        //当调用ITextReader接口的Read()方法时,将返回Empty字符串
        textReaderMock.Setup(x => x.Read()).Returns(string.Empty);
     
        //Action
        //将伪对象注入到被测试方法中
        var result = Document.IsValidHtml(textReaderMock.Object);
     
        //Assert
        Assert.IsFalse(result);
    }
    

    Setup+Callback

    当指定方法被调用时,可以收到一个回调函数,方法调用的参数将作为回调函数的参数传递。

    //被依赖的第三方接口
    public interface IPaint
    {
        void AddElement(int element);
        bool CouldBeSelected();
        event Action<int, int> SelectionChanged;
    }
    //IPaint接口的业务方
    private readonly IPaint _paint;
    public Document(IPaint paint)
    {
        _paint = paint;
    }
    public void AddElements(IEnumerable<int> elements)
    {
        foreach (var i in elements.ToList())
        {
            _paint.AddElement(i);
        }
    }
    
    
    //AddElements的单元测试方法
    [TestMethod]
    public void AddElements_MultiElements_ShouldCallAddElementsMultiTimes()
    {
        var paintMock = new Mock<IPaint>();
        var input = new List<int>()
        {
            0,1,2,3,4
        };
        var expected = new List<int>();
        //当调用IPaint接口的AddElement方法,且参数是任意int时,出发回掉函数
        paintMock.Setup(x => x.AddElement(It.IsAny<int>())).Callback<int>(i =>
        {
            expected.Add(i);
        });
        var document = new Document(paintMock.Object);
        document.AddElements(input);
        CollectionAssert.AreEqual(input, expected);
    }
    

    Setup+Sequence

    设置对方法对连续调用的响应。

    [TestMethod]
     public void GetPaintCouldBeSelected_CallTwoTimes_ReturnTrueAndFalse()
     {
         var paintMock = new Mock<IPaint>();
         //连续调用CouldBeSelected方法时,第一次返回true,第二次返回false
         paintMock.SetupSequence(x => x.CouldBeSelected()).Returns(true).Returns(false);
     
         var document = new Document(paintMock.Object);
     
         var result = document.GetPaintCouldBeSelected();
         Assert.IsTrue(result);
         result = document.GetPaintCouldBeSelected();
         Assert.IsFalse(result);
     }
    

    Verify+Times

    对一个方法调用次数进行验证。

    [TestMethod]
    public void AddElements_SingleElement_ShouldCallAddElement()
    {
        var paintMock = new Mock<IPaint>();
        //将以任意int作为参数的AddElement方法的调用进行标记,在调用paintMock.Verify方法时对AddElement方法是否经过调用进行验证
        paintMock.Setup(x => x.AddElement(It.IsAny<int>())).Verifiable();
        //----------
        //做了一些事情,比如调用了IPaint的AddElement方法
        //----------
        var document = new Document(paintMock.Object);
        document.AddElements(new List<int>() { 1, 2, 3 });
        //验证AddElement是否经过调用
        paintMock.Verify();
    }
    

    当需要对调用次数做限制时,也可以使用另一种方式:

    [TestMethod]
    public void AddElements_SingleElement_ShouldCallAddElement()
    {
        var paintMock = new Mock<IPaint>();
        paintMock.Verify(x => x.AddElement(It.IsAny<int>()), Times.Between(1, 3, Range.Inclusive));
        //----------
        //做了一些事情,比如调用了IPaint的AddElement方法
        //----------
        var document = new Document(paintMock.Object);
        document.AddElements(new List<int>() { 1, 2, 3 });
        //验证AddElement的调用次数是否为1-3
        Mock.Verify(paintMock);
    }
    

    Setup+Callbase(调用基类的方法)

    [TestMethod()]
    public void DeleteFileTest()
    {
        // arrange
        string key = "";
        var mockDI = new Mock<IFileAdapter>();
        mockDI.Setup(it => it.DeleteFile(key));
        var mockDI2 = new Mock<IConfiguration>();
        var mockDI3 = new Mock<ILoggerFactory>();
        var controller = new FileController(mockDI2.Object, mockDI3.Object, mockDI.Object);
    
        // action
        var actual = controller.DeleteFile(key);
    
        // asert
        mockDI.Verify(it => it.DeleteFile(key), Times.AtMostOnce());
    }
    

    Setup+Get(获取属性)

    Setup+Set(设置属性)

  • 相关阅读:
    Gradle Gretty进行runAppDebug的Listening for transport dt_socket at address: 5005 的后续配置
    Oracle :value too large for column "SCHEMA"."TABLE"."COLUMN" (actual: 519, maximum: 500)的解决方案
    js file对象 文件大小转换可视容易阅读的单位
    JS的Event各种属性级target/currentTarget/relatedTarget各种目录的解释
    浏览器控制台是否打开的一些措施的讨论
    eclipse启动指定jvm的版本
    IDEA terminal无法从vim的编辑模式转换为命令模式
    win7 64位系统在IronPython2.7 rc安装后运行出现"ipy64/ipy.exe"does not exist解决办法
    VS2010 插件 CSS3 IS 2.1.1 在win7 64位机子上安装小记
    Asp.net ajax 1.0 绑定drowdownlist时取值问题
  • 原文地址:https://www.cnblogs.com/wesson2019-blog/p/13454343.html
Copyright © 2020-2023  润新知