使用 Moq 和 Ninject 模拟并注入您的服务






4.75/5 (6投票s)
设计您的解决方案,并将您的类编码为松耦合对象。了解如何使用 MOQ 和 Ninject 来模拟您的服务,并在运行时使用 Ninject 进行注入。
引言
最近,我一直为解决一个与解决方案设计相关的问题而苦恼。我一直在从事产品开发,而不是项目本身,特别是在前端方面。
恰巧我需要测试组件绑定,但是由于 WebService
尚未就绪,我手中只有服务规范。
不良实践会建议编写带有大量硬编码数据的 POCO 类,并在需要这些数据的地方实例化这些类。这将导致在需要从 Web 服务的数据切换到模拟类的数据时,编写注释/取消注释代码块。最终可能有效,但我认为我们可以做得更好,不是吗?
此外,其中一项规范是确保该应用程序即使在演示环境而非实时环境中也能正常工作。不良实践会建议我部署两个应用程序版本,一个指向 Web 服务,另一个使用模拟类。这种解决方案从各方面都会有问题,迟早我会沉没。天哪!
如果我的代码“依赖于抽象而非具体实现”会怎样?如果我能在运行时绑定真实的 Service 或模拟的 one 呢?如果我不用关心这件事呢?
那将是一个梦想。
或者,我可能只需要学习如何使用 Moq 模拟 Web 服务,以及如何使用 Ninject 注入它。
所以大声说一次,大声地说出来:我将这样做,我将感到自豪。
背景
本文介绍了如何使用 Moq 框架来模拟服务,并根据依赖注入设计原则和 Ninject 作为注入器,注入 Mock 类或生产版本。
如果您不了解什么是依赖注入,请花 5 分钟时间阅读这篇文章。
如果您想了解如何在 120 秒内设置 Ninject,请查看这篇文章。
如果您想了解更多关于 Moq 和 Ninject 的信息,请随时访问这些链接
Using the Code
让我们编写一个 Client 类,负责将用户登录到我们的应用程序。系统必须能够自动使用其 Windows 身份凭据登录用户,或者,如果未识别,则通过用户名和密码登录表单进行登录。
我们负责设计前端,并且我们知道 Web 服务将公开两个方法
GET
-getCurrentUser()
POST
-validateUser(string username, string password)
这两个方法都将返回一个包含用户详细信息的 UserDTO
。IsAuthenticated
属性 需要用于检查用户是否已被服务器认证。
我们希望能够使用 ServiceMock
类进行测试或演示目的,或者使用实现通过 Http 调用 WebService
的 Service
类。
为了在运行时注入 ServiceMock
或 Service
类,我们首先编写需要两个类都实现的接口(注意:快速回顾依赖注入,请查看第 1 部分的链接。)
public interface IService {
UserDTO getCurrentUser();
UserDTO postValidateUser(string userName, string passWord);
}
易如反掌!
真正的 Service
类大概是这样的
public class Service: IService {
public UserDTO getCurrentUser() {
// ... Target URL.
string URL = "https:///Accounts/CurrentUser";
UserDTO currentUser = getUserUsingHTTPClient(URL).Result;
return currentUser;
}
public UserDTO postValidateUser(string userName, string passWord) {
// ... Target URL
string URL = "https:///Accounts/ValidateUser";
ValidateUserRequest vur = new ValidateUserRequest();
vur.userName = userName;
vur.passWord = passWord;
UserDTO validatedUser = postUserUsingHTTPClient(URL, vur).Result;
return validatedUser;
}
}
而真正的乐趣在于模拟那个类。
让我们首先从 NuGet 获取 MOQ,然后查看GitHub。
public class ServiceMock : IService
{
Mock<IService> _service { get; set; }
public ServiceMock()
{
_service = new Mock<IService>();
// Setup getCurrentUser() case
_service.Setup(x => x.getCurrentUser()).Returns(getCurrentIdentity());
// Setup postValidateUser(name, pwd) case
// Password matches
_service.Setup(x => x.postValidateUser(It.IsAny<string>(),
It.Is<string>(w => w.Equals("MoqAndNinject") )) ).Returns
((string userName, string password) => { return new UserDTO(userName, true); });
// Password does not match
_service.Setup(x => x.postValidateUser(It.IsAny<string>(),
It.Is<string>(w => !w.Equals("MoqAndNinject")))).Returns
((string userName, string password) => { return new UserDTO("DontTryToHackMe", false); });
}
public UserDTO getCurrentUser()
{
return _service.Object.getCurrentUser();
}
public UserDTO postValidateUser(string userName, string passWord)
{
return _service.Object.postValidateUser(userName, passWord);
}
private UserDTO getCurrentIdentity()
{
return new UserDTO(WindowsIdentity.GetCurrent().Name, true);
}
}
getCurrentUser()
将返回我们的 Windows 身份名称postValidateUser(string userName, string passWord)
将使用您输入的任何名称进行身份验证,但前提是您输入的密码是“MoqAndNinject
”这个非常秘密的密码。
完成。
现在我们只需要将 ServiceMock
注入到 ViewModel 中。如果您已经查看了第一部分中的链接,这将花费 60 秒。
我们的 Module 将如下所示
public class DIModule : NinjectModule
{
public override void Load()
{
Bind<IService>().To<ServiceMock>(); // if using the Mock
// Bind<IService>().To<Service>(); // if using the Service
}
}
然后,我们将在客户端使用注入的类
public IService service { get; set; }
public MainViewModel()
{
service = IocKernel.Get<IService>();
}
关注点
一旦设置好,就可以轻松自定义 Mock Service 并将其用于测试。我想展示如何设置 MockObject
以从类中的方法(例如 getCurrentIdentity()
)返回一个对象,您可以在其中实现自定义逻辑并返回所需的对象。
或者,如果您像我一样懒惰,只需通过管理可能出现的场景来设置 Mock
对象(即,在 postValidateUser
的情况下,这些场景只有两种
- 用户输入正确的密码
- 用户输入任何其他密码
这里的重点是,如何在运行时模拟(Mock
)一个类并将其注入。因此,我只是简要介绍了 Service
类将如何实现。没有必要详细介绍如何使用 HTTPClient
来获取 POST 请求到 Web 服务。
附件示例的工作原理
这是一个简单的 WPF 项目,采用 MVVM 方法进行编码。
首先,窗口显示时会使用您的 Windows 身份名称进行识别。复选框“IsAuthenticated
”将被选中。
您可以取消选中它,从而启用“确定”按钮,该按钮将尝试通过您在 UserName
和 Password
文本框中输入的详细信息来验证您的身份。如果您想被认证,请记住使用那个非常秘密的密码。
我建议下载它并查看这些类,因为我添加了一些有用的注释,可以帮助您更好地理解整个故事。