当前位置: 首页 > 工具软件 > moq > 使用案例 >

Moq的使用方法

巫马自明
2023-12-01

我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的时候不真正的将数据持久化。这种隔离我们一般使用抽象的方式,也就是利用接口或抽象类将持久化层隔离开,然后利用mock来模拟相应的接口或抽象类来完成相应的持久化类。Moq就是这种Mock框架之一。
Moq(发音为“Mock-you”或者只是“Mock”)可以充分利用.NET的Linq表达式树和lambda表达式,这使它成为最高效,类型安全的提供重构友好的模拟库。它支持模拟接口和类。它的API非常简单直接,不需要任何先前的知识或经验来模拟概念。
具体的语法问题可以参考Moq的快速入门

下面用的都是xuint的例子

方法

public interface ITest
{
    string Test();
}

测试代码

[Fact]
public void TestTest()
{
    var test = new Mock<ITest>();
    test.Setup(p => p.Test()).Returns("lfm");
   Assert.Equal("lfm", test.Object.Test());
}

匹配参数

public interface IMatchTest
{
    string Test(int test);
}

测试代码

var testMatch = new Mock<IMatchTest>();
testMatch.Setup(p => p.Test(It.Is<int>(i => i % 2 == 0))).Returns("偶数");
testMatch.Setup(p => p.Test(It.Is<int>(i => i % 2 != 0))).Returns("奇数");
Assert.Equal("偶数", testMatch.Object.Test(4));
Assert.Equal("奇数", testMatch.Object.Test(3));

上边测试代码模拟实现IMathTest接口实例,其中如果Test方法的参数是偶数,其返回值为“偶数”。这里的IT用来过滤参数的类,其具体解释可以参见MoQ的文档

属性

public interface IPropertiesTest
{
     int Test { get; set; }
}

测试代码

var testProperties = new Mock<IPropertiesTest>();
testProperties.Setup(p => p.Test).Returns(1);
Assert.Equal(1, testProperties.Object.Test);

或者

var testProperties = new Mock<IPropertiesTest>();
testProperties.SetupProperty(p => p.Test,1);
Assert.Equal(1, testProperties.Object.Test);

Callback

当执行某方法时调用其内部输入的Action委托

int count = 0;
var testProperties = new Mock<IPropertiesTest>();
testProperties.Setup(p => p.Test).Returns(1).Callback(()=>count++);
Assert.Equal(1, testProperties.Object.Test);
Assert.Equal(1, count);

在调用Test方法是执行了count++

Verification

判断某方法或属性是否执行过
如果代码如下:

var testProperties = new Mock<IPropertiesTest>();
testProperties.Setup(p => p.Test).Returns(1);
testProperties.Verify(p => p.Test);
Assert.Equal(1, testProperties.Object.Test);

会抛出异常,因为第3行执行时Test方法还没有被调用过,改为如下代码可以通过测试

var testProperties = new Mock<IPropertiesTest>();
testProperties.Setup(p => p.Test).Returns(1);
Assert.Equal(1, testProperties.Object.Test);
testProperties.Verify(p => p.Test);

实际的单元测试

[HttpPost("DelyTime")]
public ScriptOutput DelyTime()
{
    try
    {
        return scriptService.DelyTime(GetScriptInput());
    }
    catch (Exception ex)
    {
        return null;
    }
}
private ScriptInput GetScriptInput()
{
    byte[] datas = new byte[HttpContext.Request.ContentLength.Value];
    HttpContext.Request.Body.Read(datas, 0, datas.Length);
    string stream = System.Text.Encoding.UTF8.GetString(datas);
    return JsonConvert.DeserializeObject<ScriptInput>(stream);
}

测试上面DelyTime()方法,会发现,他有调用到GetScriptInput(),然后里面有调用到HttpContext。但是我们现在是在单元测试,并没有具体的浏览器环境,那么就要用到Moq来模拟一下。

[Fact]
public void DelyTime()
{
    var mock = new Mock<IScriptService>();
    var requestMock = new Mock<HttpRequest>();
    var contextMock = new Mock<HttpContext>();
    var streamMock = new Mock<System.IO.Stream>();
    ServiceController serviceController = new ServiceController(mock.Object);
    ScriptOutput scriptOutput = new ScriptOutput();
    serviceController.ControllerContext = new ControllerContext();
    serviceController.ControllerContext.HttpContext = contextMock.Object;

    requestMock.SetupGet(x => x.ContentLength).Returns(0);
    requestMock.SetupGet(x => x.Body).Returns(streamMock.Object);
    contextMock.SetupGet(x => x.Request).Returns(requestMock.Object);
    mock.Setup(x => x.DelyTime(It.IsAny<ScriptInput>())).Returns(scriptOutput);
    var result = serviceController.DelyTime();
    Assert.Equal(scriptOutput, result);
}

其中的HttpContext也可以不用Mock用下列的方式

serviceController.ControllerContext.HttpContext = new DefaultHttpContext();
 类似资料: