【问题标题】:Testing property used on mvc controller after injection注入后在 mvc 控制器上使用的测试属性
【发布时间】:2016-10-24 10:44:09
【问题描述】:
public class MyUser: IIdentity, IMyUser{
   // ommited for abbrev.       
}

public interface IMyUser
{
    int Id { get; set; }
    int? CompanyId { get; set; }        
}

在 MyController 中,除了其他人之外,我还使用 MyUser 来填充 组合框

public ActionResult Details(int? subsidId = null, int? req = null)
 {
    ...
    MyUser user = this.User.GetInfo();
    var obj1 = ... // ommited on purpose for abbrev.    
    populateCombos(subsidId, user.CompanyId, req); 
 }

我在 populateCombos 这一行遇到异常,因为用户对象始终为空。 在同一个控制器中,我正在注入 IMyUser 实现的接口

[Inject]
public IMyUser MyUser { get; set; }

此属性已使用 ninject 正确绑定(与我应用中的其他属性一样)

kernel.Bind<IMyUser>().To<MyUser>().InRequestScope();

现在在测试项目中,我正在使用模拟请求的依赖项初始化控制器

[SetUp]
public void Setup()
{
   _controller = new MyController(){
       ... repositories....
       MyUser = MockMyUser()
   }
}

private IMyUser MockMyUser()
{
    var u = new Mock<IMyUser>();
    u.SetupGet(x => x.Id).Returns(1);      
    u.SetupGet(x => x.CompanyId).Returns(99);    

    return u.Object;
}

在测试方法里面我写了简单的测试

[Test]
public void CanDoDetails()
{
   ViewResult res = this.controller.Details(1, 2) as ViewResult;  

   var model = result.Model as MyModel;            

   Assert.IsNotNull(model);
}

问题是:

为什么我会得到这种依赖(MyUserMyControllernull) 导致它被正确注入?我做错了什么?

更新:

public static MyUser GetInfo(this IPrincipal principal)
        {
            if (principal != null)
            {
                return principal.Identity as MyUser;    
            }

            return null;
        }

更新 2: 根据下面的 Nkosi 回答,我进行了以下更改

public interface IMyUser : IIdentity { ... }

和内部细节ActionMethod控制器

IMyUser user = this.User.GetInfo();

在测试方法下

[SetUp]
public void Setup()
{
    var mockUser = MockMyUser();
     string[] roles = new[] { "Admin", "SuperUser" };
    _controller = new MyController()
    {
       ....
       MyUser = this.MockMyUser(),
       ControllerContext = new ControllerContext() {
          Controller = _controller,
          RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
       }
    }

}
but I'm still getting `IMyUser user = this.User.GetInfo();`

`this.User` is still null.

附言我还将 GetInfo 更改为返回 IMyUser 而不是 MyUser

【问题讨论】:

  • this.User是与控制器关联的Principal,假设GetInfo()是一个扩展方法,基于该principal创建你的MyUser
  • 是的,恩科西,没错。
  • this.User.GetInfo() 的调用通过返回一个空对象导致了问题。听起来GetInfo 也需要被模拟出来。
  • 我用 GetInfo 方法更新了这个问题。基本上这个方法只在测试环境下返回null,在应用上就可以了。
  • 它返回 null,然后您尝试调用 null MyUser 对象上的 CompanyId 属性。您将不得不对返回的空引用做一些事情。你可以创建更多的模拟对象,重构你的代码,或者伪造CompanyId。这取决于您要测试的内容。

标签: c# asp.net-mvc unit-testing nunit moq


【解决方案1】:

还模拟 controller 怎么样?

Mock<MyController> mockController = new Mock<MyController>();
mockController.SetupGet(t => t.MyUser).Returns(MockMyUser());

然后您可以通过 mockController.Object 访问您的控制器并尝试您的测试。

【讨论】:

  • 不,它已经被模拟了,我已经提到过控制器内的 MyUser 属性可以通过调试获得。
  • 那么,您的问题是您无法在测试中或在运行应用程序时设置它吗?
  • 内部测试,而不是应用程序本身。
  • 那么问题到底出在哪里? “...控制器内的 MyUser 属性可通过调试获得。”是什么意思?我看到你从你没有提供的东西中得到 null (MyUser user = ....;) 什么代码而不是省略号?
  • 刚刚更新了问题。是的,确切地说,我也没有看到问题,但它存在。槽调试 IMyUser 已正确注入到控制器中,但我在 populatingCombos 方法上将 MyUser 设为 null。
【解决方案2】:

我的建议也是让IMyUser 继承自IIdentity

public class MyUser: IMyUser {
   // ommited for abbrev.       
}

public interface IMyUser: IIdentity {
    int Id { get; set; }
    int? CompanyId { get; set; }        
}

如果您使用Controller.User { get; },要让您的单元测试正常工作,就是为控制器创建一个模拟/假用户。然而,为了访问只读的用户,您必须创建一个模拟HttpContext。呸。幸运的是,无论如何您只想访问User

private class MockHttpContext : HttpContextBase {
    private readonly IPrincipal user;

    public MockHttpContext(IIdentity identity , string[] roles = null) {
        var principal = new GenericPrincipal(identity, roles ?? new string[] { });
        user = principal;
    }

    public override IPrincipal User {
        get {
            return user;
        }
        set {
            base.User = value;
        }
    }
}

您可以根据您在运行时应用的任何声明来设置主体以适应您的身份验证设置。这只是一个例子。

[SetUp]
public void Setup()
{
   string[] roles = new[] { "Admin", "SuperUser" };
   var mockUser = MockMyUser();

   _controller = new MyController(){
       ... repositories....
       MyUser = mockUser
   };

   _controller.ControllerContext = new ControllerContext() {
        Controller = _controller,
        RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
    };

}

private IMyUser MockMyUser()
{
    var u = new Mock<IMyUser>();
    u.Setup(x => x.Name).Returns("username@test.io");
    u.Setup(x => x.Id).Returns(1);
    u.Setup(x => x.CompanyId).Returns(99);

    return u.Object;
}

现在应该允许

public static IMyUser GetInfo(this IPrincipal principal) {
    if (principal != null) {
        return principal.Identity as IMyUser;    
    }
    return null;
}

返回principal.Identity as IMyUser 不为空。

更新

我使用上面为您提供的内容重新创建了您的测试的最小版本,并且能够对其进行测试并通过。

[TestClass]
public class MyUserDependentControllerTest {

    [TestMethod]
    public void CanDoDetails() {
        //Arrange
        string[] roles = new[] { "Admin", "SuperUser" };

        var u = new Mock<IMyUser>();
        u.Setup(x => x.Name).Returns("username@test.io");
        u.Setup(x => x.Id).Returns(1);
        u.Setup(x => x.CompanyId).Returns(99).Verifiable();

        var mockUser = u.Object;

        var controller = new MyController() {
            //... repositories....
            MyUser = mockUser
        };

        controller.ControllerContext = new ControllerContext() {
            Controller = controller,
            RequestContext = new RequestContext(new MockHttpContext(mockUser, roles), new RouteData())
        };

        //Act
        var result = controller.Details(1, 2);

        //Assert
        var viewResult = result as ViewResult;
        Assert.IsNotNull(viewResult);

        var model = viewResult.Model as MyModel;
        Assert.IsNotNull(model);

        u.Verify();
    }

    public class MyController : Controller {
        public ActionResult Details(int? subsidId = null, int? req = null) {
            //...
            var user = this.User.GetInfo();
            //
            populateCombos(subsidId, user.CompanyId, req);

            //this is just for matching test expectations
            var model = new MyModel();
            return View(model);
        }

        private void populateCombos(int? subsidId, int? nullable, int? req) {
            //Empty as I have no clue what happens in here
        }

        public IMyUser MyUser { get; set; }
    }

    public class MyModel { }
}

【讨论】:

  • 感谢您的信息,我使用了这种方法,但仍然有相同的错误消息。 Controller = 控制器行中的控制器变量到底是什么?在 ActionMethod 的内部调试中,我得到了 MyUser user = this.User.GetInfo();空。
  • 错字应该是_controller,将修复。你应该显示GetInfo,这样我就可以看到它返回null。
  • 我用 GetInfo 方法更新了这个问题。基本上这个方法只在测试环境下返回null,在应用上就可以了。
  • @user1765862 我根据提供的其他详细信息更新了答案。
  • 我更新了问题。仍在为同样的异常而苦苦挣扎。
猜你喜欢
  • 2019-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
相关资源
最近更新 更多