我有一个简单的“服务”系统,其界面如下所示。我试图模拟它以用于我的单元测试,但遇到了一些障碍。它的工作方式是我设计实现 IRequestFor<T,R>
的类我会这样调用服务总线...
var member = new Member { Name = "valid@email.com", Password = "validPassword" };
ServiceBus.Query<ValidateUser>().With(member);
这在我的代码中运行良好。我没有问题。但是当我尝试像这样 mock 它时..
var service = Mock.Create<IServiceBus>();
// Model
var model = new Web.Models.Membership.Login
{
Email = "acceptible@email.com",
Password = "acceptiblePassword",
RememberMe = true
};
// Arrange
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>().With(model))
.Returns(true);
我收到以下错误。
NullReferenceException
我什至不知道异常是什么。它“指向”我的 Controller 代码中的 ServiceBus,如果我使用调试器,该对象就像 .. {IServiceBus_Proxy_2718486e043f432da4b143c257cef8ce}
,但除此之外,其他一切看起来都与我在正常运行中单步执行时完全相同。
我正在使用 Telerik JustMock 进行模拟,但我也不知道如何在不同的模拟框架中执行此操作。我也在使用 Ninject 进行依赖注入(inject)。谁能帮帮我?
为方便起见,我在下面包含了尽可能多的代码。
代码引用
服务总线
public interface IServiceBus
{
T Query<T>() where T : IRequest;
T Dispatch<T>() where T : IDispatch;
}
public interface IRequest
{
}
public interface IDispatch
{
}
public interface IRequestFor<TResult> : IRequest
{
TResult Reply();
}
public interface IRequestFor<TParameters, TResult> : IRequest
{
TResult With(TParameters parameters);
}
public interface IDispatchFor<TParameters> : IDispatch
{
void Using(TParameters parameters);
}
服务总线实现
public class ServiceBus : IServiceBus
{
private readonly IKernel kernel;
public ServiceBus(IKernel kernel) {
this.kernel = kernel;
}
/// <summary>
/// Request a query behavior that may be given parameters to yield a result.
/// </summary>
/// <typeparam name="T">The type of query to request.</typeparam>
/// <returns></returns>
public T Query<T>() where T : IRequest
{
// return a simple injected instance of the query.
return kernel.Get<T>();
}
/// <summary>
/// Request a dispatch handler for a given query that may be given parameters to send.
/// </summary>
/// <typeparam name="T">The type of handler to dispatch.</typeparam>
/// <returns></returns>
public T Dispatch<T>() where T : IDispatch
{
// return a simple injected instance of the dispatcher.
return kernel.Get<T>();
}
}
服务总线依赖注入(inject)接线(Ninject)
Bind<IServiceBus>()
.To<ServiceBus>()
.InSingletonScope();
完成单元测试
[TestMethod]
public void Login_Post_ReturnsRedirectOnSuccess()
{
// Inject
var service = Mock.Create<IServiceBus>();
var authenticationService = Mock.Create<System.Web.Security.IFormsAuthenticationService>();
// Arrange
var controller = new Web.Controllers.MembershipController(
service, authenticationService
);
var httpContext = Mock.Create<HttpContextBase>();
// Arrange
var requestContext = new RequestContext(
new MockHttpContext(),
new RouteData());
controller.Url = new UrlHelper(
requestContext
);
// Model
var model = new Web.Models.Membership.Login
{
Email = "acceptible@email.com",
Password = "acceptiblePassword",
RememberMe = true
};
// Arrange
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>().With(model))
.Returns(true);
// Act
var result = controller.Login(model, "/Home/");
// Assert
Assert.IsInstanceOfType(result, typeof(RedirectResult));
}
实际查询方法
public class ValidateMember : IRequestFor<IValidateMemberParameters, bool>
{
private readonly ISession session;
public ValidateMember(ISession session) {
this.session = session;
}
public bool With(IValidateMemberParameters model)
{
if (String.IsNullOrEmpty(model.Email)) throw new ArgumentException("Value cannot be null or empty.", "email");
if (String.IsNullOrEmpty(model.Password)) throw new ArgumentException("Value cannot be null or empty.", "password");
// determine if the credentials entered can be matched in the database.
var member = session.Query<Member>()
.Where(context => context.Email == model.Email)
.Take(1).SingleOrDefault();
// if a member was discovered, verify their password credentials
if( member != null )
return System.Security.Cryptography.Hashing.VerifyHash(model.Password, "SHA512", member.Password);
// if we reached this point, the password could not be properly matched and there was an error.
return false;
}
}
登录 Controller 操作
[ValidateAntiForgeryToken]
[HttpPost]
public ActionResult Login(Web.Models.Membership.Login model, string returnUrl)
{
if (ModelState.IsValid)
{
// attempt to validate the user, and if successful, pass their credentials to the
// forms authentication provider.
if (Bus.Query<ValidateMember>().With(model))
{
// retrieve the authenticated member so that it can be passed on
// to the authentication service, and logging can occur with the
// login.
Authentication.SignIn(model.Email, model.RememberMe);
if (Url.IsLocalUrl(returnUrl))
return Redirect(returnUrl);
else
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
登录 View 模型
public class Login : Membership.Messages.IValidateMemberParameters
{
[Required]
[DataType(DataType.EmailAddress)]
[RegularExpression(@"^[a-z0-9_\+-]+(\.[a-z0-9_\+-]+)*@(?:[a-z0-9-]+){1}(\.[a-z0-9-]+)*\.([a-z]{2,})$", ErrorMessage = "Invalid Email Address")]
[Display(Name = "Email Address")]
public string Email { get; set; }
[Required]
[StringLength(32, MinimumLength = 6)]
[DataType(DataType.Password)]
[RegularExpression(@"^([a-zA-Z0-9@#$%]){6,32}$", ErrorMessage = "Invalid Password. Passwords must be between 6 and 32 characters, may contain any alphanumeric character and the symbols @#$% only.")]
[Display(Name = "Password")]
public string Password { get; set; }
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
最佳答案
我对 JustMock 在递归/嵌套模拟方面的工作方式没有任何实际经验,但看起来 at the documentation看起来只有当您的中间链成员是属性时,这种模拟才有效。而你正试图隐式地模拟 IServiceBus
方法,这是通用的,也可能是一个障碍。
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>().With(model))
.Returns(true);
您想在此处设置期望 With
方法来自 ValidateMember
,假设 Query<T>
IServiceBus
上的方法将被自动模拟,这可能不是这种情况。
这里应该做的是更“传统”地模拟它,分两步 - 首先模拟你的 Query<T>
IServiceBus
上的方法返回 ValidateMember
的模拟,你应该模拟返回 true
.
var validateMemberMock = Mock.Create<Membership.Messages.ValidateMember>();
Mock.Arrange(() => service.Query<Membership.Messages.ValidateMember>())
.Returns(validateMemberMock);
Mock.Arrange(() => validateMemberMock.With(model))
.Returns(true);
编辑 这是我的传递代码,与您的传递代码相同:
[TestClass]
public class JustMockTest
{
public interface IServiceBus
{
T Query<T>() where T : IRequest;
}
public interface IRequest
{
}
public interface IRequestFor<TParameters, TResult> : IRequest
{
TResult With(TParameters parameters);
}
public class ValidateMember : IRequestFor<IValidateMemberParameters, bool>
{
public bool With(IValidateMemberParameters model)
{
return false;
}
}
public class MembershipController
{
private IServiceBus _service;
public MembershipController(IServiceBus service)
{
_service = service;
}
public bool Login(Login model)
{
return _service.Query<ValidateMember>().With(model);
}
}
public interface IValidateMemberParameters
{
}
public class Login : IValidateMemberParameters
{
public string Email;
public string Password;
public bool RememberMe;
}
[TestMethod]
public void Login_Post_ReturnsRedirectOnSuccess()
{
// Inject
var service = Mock.Create<IServiceBus>();
// Arrange
var controller = new MembershipController(service);
// Model
var model = new Login
{
Email = "acceptible@email.com",
Password = "acceptiblePassword",
RememberMe = true
};
var validateMemberMock = Mock.Create<ValidateMember>();
Mock.Arrange(() => service.Query<ValidateMember>())
.Returns(validateMemberMock);
Mock.Arrange(() => validateMemberMock.With(model)).IgnoreArguments()
.Returns(true);
// Act
var result = controller.Login(model);
// Assert
Assert.IsTrue(result);
}
}
关于c# - 在 ASP.NET MVC 中模拟一个简单的服务总线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5663435/