c# - 在 ASP.NET MVC 中模拟一个简单的服务总线

标签 c# unit-testing asp.net-mvc-3 mocking justmock

我有一个简单的“服务”系统,其界面如下所示。我试图模拟它以用于我的单元测试,但遇到了一些障碍。它的工作方式是我设计实现 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; }
}

enter image description here

最佳答案

我对 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/

相关文章:

asp.net-mvc-3 - asp.net mvc 将复选框值保存到数据库

c# - 过滤不需要的 Blob (检测图像中的数字)

c# - 使用 Lucene.NET 搜索过滤器

c# - 如何使用 MYSQL 在 C# 中的 datagridview 中插入、删除、选择、更新值

oop - 单元测试不同的类层次结构

c# - 如何强制 Microsoft Azure .NET API 调用失败?

unit-testing - Scrutinizer 表示 Travis 通知了 "tests failed",但测试确实通过了

c# - (byte)Convert.ToChar(anyStringOfLengthOne) 怎么可能抛出错误?

javascript - mvc3 razor - url.action javascript 错误 onClick 事件

c# - 从我的 C# 函数更新 Javascript 文件的变量