免费建立国外网站,服务器平台,短链接生成器app,泰州做房产的网站Mock 框架 Moq 的使用IntroMoq 是 .NET 中一个很流行的 Mock 框架#xff0c;使用 Mock 框架我们可以只针对我们关注的代码进行测试#xff0c;对于依赖项使用 Mock 对象配置预期的依赖服务的行为。Moq 是基于 Castle 的动态代理来实现的#xff0c;基于动态代理技术动态生成… Mock 框架 Moq 的使用IntroMoq 是 .NET 中一个很流行的 Mock 框架使用 Mock 框架我们可以只针对我们关注的代码进行测试对于依赖项使用 Mock 对象配置预期的依赖服务的行为。Moq 是基于 Castle 的动态代理来实现的基于动态代理技术动态生成满足指定行为的类型在一个项目里, 我们经常需要把某一部分程序独立出来以便我们可以对这部分进行测试. 这就要求我们不要考虑项目其余部分的复杂性, 我们只想关注需要被测试的那部分. 这里就需要用到模拟(Mock)技术.因为, 请仔细看. 我们想要隔离测试的这部分代码对外部有一个或者多个依赖. 所以编写测试代码的时候, 我们需要提供这些依赖. 而针对隔离测试, 并不应该使用生产时用的依赖项, 所以我们使用模拟版本的依赖项, 这些模拟版依赖项只能用于测试时, 它们会使隔离更加容易.img绿色的是需要被测试的类,黄色是Mock的依赖项——引用自杨旭大佬的博文Prepare首先我们需要先准备一下用于测试的类和接口下面的示例都是基于下面定义的类和方法来做的public interface IUserIdProvider
{string GetUserId();
}
public class TestModel
{public int Id { get; set; }
}
public interface IRepository
{int Version { get; set; }int GetCount();Taskint GetCountAsync();TestModel GetById(int id);ListTestModel GetList();TResult GetResultTResult(string sql);int GetNumT();bool Delete(int id);
}public class TestService
{private readonly IRepository _repository;public TestService(IRepository repository){_repository repository;}public int Version{get _repository.Version;set _repository.Version value;}public ListTestModel GetList() _repository.GetList();public TResult GetResultTResult(string sql) _repository.GetResultTResult(sql);public int GetResult(string sql) _repository.GetResultint(sql);public int GetNumT() _repository.GetNumT();public int GetCount() _repository.GetCount();public Taskint GetCountAsync() _repository.GetCountAsync();public TestModel GetById(int id) _repository.GetById(id);public bool Delete(TestModel model) _repository.Delete(model.Id);
}
我们要测试的类型就是类似 TestService 这样的而 IRepositoyTestModel 和 IUserIdProvider 是属于外部依赖Mock MethodGet Started通常我们使用 Moq 最常用的可能就是 Mock 一个方法了最简单的一个示例如下[Fact]
public void BasicTest()
{var userIdProviderMock new MockIUserIdProvider();userIdProviderMock.Setup(x x.GetUserId()).Returns(mock);Assert.Equal(mock, userIdProviderMock.Object.GetUserId());
}
Match Arguments通常我们的方法很多是带有参数的在使用 Moq 的时候我们可以通过设置参数匹配为不同的参数返回不同的结果来看下面的这个例子[Fact]
public void MethodParameterMatch()
{var repositoryMock new MockIRepository();repositoryMock.Setup(x x.Delete(It.IsAnyint())).Returns(true);repositoryMock.Setup(x x.GetById(It.Isint(_ _ 0))).Returns((int id) new TestModel(){Id id});var service new TestService(repositoryMock.Object);var deleted service.Delete(new TestModel());Assert.True(deleted);var result service.GetById(1);Assert.NotNull(result);Assert.Equal(1, result.Id);result service.GetById(-1);Assert.Null(result);repositoryMock.Setup(x x.GetById(It.Isint(_ _ 0))).Returns(() new TestModel(){Id -1});result service.GetById(0);Assert.NotNull(result);Assert.Equal(-1, result.Id);
}
通过 It.IsAnyT 来表示匹配这个类型的所有值通过 It.IsT(ExpressionFuncbool) 来设置一个表达式来断言这个类型的值通过上面的例子我们可以看的出来设置返回值的时候可以直接设置一个固定的返回值也可以设置一个委托来返回一个值也可以根据方法的参数来动态配置返回结果Async Method现在很多地方都是在用异步方法Moq 设置异步方法有三种方式一起来看一下示例[Fact]
public async Task AsyncMethod()
{var repositoryMock new MockIRepository();// Task.FromResultrepositoryMock.Setup(x x.GetCountAsync()).Returns(Task.FromResult(10));// ReturnAsyncrepositoryMock.Setup(x x.GetCountAsync()).ReturnsAsync(10);// Mock Result, start from 4.16repositoryMock.Setup(x x.GetCountAsync().Result).Returns(10);var service new TestService(repositoryMock.Object);var result await service.GetCountAsync();Assert.True(result 0);
}
还有一个方式也可以但是不推荐编译器也会给出一个警告就是下面这样repositoryMock.Setup(x x.GetCountAsync()).Returns(async () 10);
Generic Type有些方法会是泛型方法对于泛型方法我们来看下面的示例[Fact]
public void GenericType()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.Setup(x x.GetResultint(It.IsAnystring())).Returns(1);Assert.Equal(1, service.GetResult());repositoryMock.Setup(x x.GetResultstring(It.IsAnystring())).Returns(test);Assert.Equal(test, service.GetResultstring());
}[Fact]
public void GenericTypeMatch()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.Setup(m m.GetNumIt.IsAnyType()).Returns(-1);repositoryMock.Setup(m m.GetNumIt.IsSubtypeTestModel()).Returns(0);repositoryMock.Setup(m m.GetNumstring()).Returns(1);repositoryMock.Setup(m m.GetNumint()).Returns(2);Assert.Equal(0, service.GetNumTestModel());Assert.Equal(1, service.GetNumstring());Assert.Equal(2, service.GetNumint());Assert.Equal(-1, service.GetNumbyte());
}
如果要 Mock 指定类型的数据可以直接指定泛型类型如上面的第一个测试用例如果要不同类型设置不同的结果一种是直接设置类型如果要指定某个类型或者某个类型的子类可以用 It.IsSubtypeT如果要指定值类型可以用 It.IsValueType如果要匹配所有类型则可以用 It.IsAnyTypeCallback我们在设置 Mock 行为的时候可以设置 callback 来模拟方法执行时的逻辑来看一下下面的示例[Fact]
public void Callback()
{var deletedIds new Listint();var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.Setup(x x.Delete(It.IsAnyint())).Callback((int id) {deletedIds.Add(id);}).Returns(true);for (var i 0; i 10; i){service.Delete(new TestModel() { Id i });}Assert.Equal(10, deletedIds.Count);for (var i 0; i 10; i){Assert.Equal(i, deletedIds[i]);}
}
Verification有时候我们会验证某个方法是否执行并不需要关注是否方法的返回值这时我们可以使用 Verification 验证某个方法是否被调用示例如下[Fact]
public void Verification()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);service.Delete(new TestModel(){Id 1});repositoryMock.Verify(x x.Delete(1));repositoryMock.Verify(x x.Version, Times.Never());Assert.ThrowsMockException(() repositoryMock.Verify(x x.Delete(2)));
}
如果方法没有被调用就会引发一个 MockException 异常verification failedVerification 也可以指定方法触发的次数比如repositoryMock.Verify(x x.Version, Times.Never);默认是 Times.AtLeastOnce可以指定具体次数 Times.Exactly(1) 或者指定一个范围 Times.Between(1,2, Range.Inclusive)Moq 也提供了一些比较方便的方法比如Times.Never()/Times.Once()/Times.AtLeaseOnce()/Times.AtMostOnce()/Times.AtLease(2)/Times.AtMost(2)Mock PropertyMoq 也可以 mock 属性property 的本质是方法加一个字段所以也可以用 Mock 方法的方式来 Mock 属性只是使用 Mock 方法的方式进行 Mock 属性的话后续修改属性值就不会引起属性值的变化了如果修改属性则要使用 SetupProperty 的方式来 Mock 属性具体可以参考下面的这个示例[Fact]
public void Property()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.Setup(x x.Version).Returns(1);Assert.Equal(1, service.Version);service.Version 2;Assert.Equal(1, service.Version);
}[Fact]
public void PropertyTracking()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.SetupProperty(x x.Version, 1);Assert.Equal(1, service.Version);service.Version 2;Assert.Equal(2, service.Version);
}
Sequence我们可以通过 Sequence 来指定一个方法执行多次返回不同结果的效果看一下示例就明白了[Fact]
public void Sequence()
{var repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);repositoryMock.SetupSequence(x x.GetCount()).Returns(1).Returns(2).Returns(3).Throws(new InvalidOperationException());Assert.Equal(1, service.GetCount());Assert.Equal(2, service.GetCount());Assert.Equal(3, service.GetCount());Assert.ThrowsInvalidOperationException(() service.GetCount());
}
第一次调用返回值是1第二次是2第三次是3第四次是抛了一个 InvalidOperationExceptionLINQ to Mocks我们可以通过 Mock.Of 来实现类似 LINQ 的方式创建一个 mock 对象实例指定类型的实例如果对象比较深要 mock 的对象比较多使用这种方式可能会一定程度上简化自己的代码来看使用示例[Fact]
public void MockLinq()
{var services Mock.OfIServiceProvider(sp sp.GetService(typeof(IRepository)) Mock.OfIRepository(r r.Version 1) sp.GetService(typeof(IUserIdProvider)) Mock.OfIUserIdProvider(a a.GetUserId() test));Assert.Equal(1, services.ResolveServiceIRepository().Version);Assert.Equal(test, services.ResolveServiceIUserIdProvider().GetUserId());
}
Mock Behavior默认的 Mock Behavior 是 Loose默认没有设置预期行为的时候不会抛异常会返回方法返回值类型的默认值或者空数组或者空枚举在声明 Mock 对象的时候可以指定 Behavior 为 Strict这样就是一个**真正**的 mock 对象没有设置预期行为的时候就会抛出异常示例如下[Fact]
public void MockBehaviorTest()
{// Make mock behave like a true Mock,// raising exceptions for anything that doesnt have a corresponding expectation: in Moq slang a Strict mock;// default behavior is Loose mock,// which never throws and returns default values or empty arrays, enumerable, etcvar repositoryMock new MockIRepository();var service new TestService(repositoryMock.Object);Assert.Equal(0, service.GetCount());Assert.Null(service.GetList());var arrayResult repositoryMock.Object.GetArray();Assert.NotNull(arrayResult);Assert.Empty(arrayResult);repositoryMock new MockIRepository(MockBehavior.Strict);Assert.ThrowsMockException(() new TestService(repositoryMock.Object).GetCount());
}
使用 Strict 模式不设置预期行为的时候就会报异常异常信息类似下面这样strict exceptionMoreMoq 还有一些别的用法还支持事件的操作还有 Protected 成员的 Mock还有一些高级的用法自定义 Default 行为等感觉我们平时可能并不太常用所以上面并没有加以介绍有需要用的可以参考 Moq 的文档上述测试代码可以在 Github 获取 https://github.com/WeihanLi/SamplesInPractice/blob/master/XunitSample/MoqTest.csReferenceshttps://github.com/moq/moq4/wiki/Quickstarthttps://github.com/moq/moq4https://github.com/WeihanLi/SamplesInPractice/blob/master/XunitSample/MoqTest.cshttps://www.cnblogs.com/tylerzhou/p/11410337.htmlhttps://www.cnblogs.com/cgzl/p/9304567.htmlhttps://www.cnblogs.com/haogj/archive/2011/07/22/2113496.html