我们在做单元测试的时候,常常困扰于数据的持久化问题,很多情况下我们不希望单元测试影响到数据库中的内容,而且受数据库的影响有时我们的单元测试的速度会很慢,所以我们往往希望将持久化部分隔离开,做单元测试的时候不真正的将数据持久化。这种隔离我们一般使用抽象的方式,也就是利用接口或抽象类将持久化层隔离开,然后利用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);
当执行某方法时调用其内部输入的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++
判断某方法或属性是否执行过
如果代码如下:
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();