单元测试ASP.NET应用MVC5 [英] Unit Testing ASP.NET MVC5 App

查看:622
本文介绍了单元测试ASP.NET应用MVC5的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我添加一个新的属性(如图教程扩展ApplicationUser类
 <一href=\"http://www.asp.net/mvc/tutorials/mvc-5/create-an-aspnet-mvc-5-app-with-facebook-and-google-oauth2-and-openid-sign-on\"相对=nofollow>与Facebook和谷歌的OAuth2和OpenID登录(C#)创建一个ASP.NET MVC应用5 )

I'm extending the ApplicationUser class by adding a new property (as shown in the tutorial Create an ASP.NET MVC 5 App with Facebook and Google OAuth2 and OpenID Sign-on (C#))

public class ApplicationUser : IdentityUser
{
    public DateTime BirthDate { get; set; }
}

现在我想创建一个单元测试来验证我的AccountController正确保存出生日期。

Now I want to create a Unit Test to verify that my AccountController is correctly saving the BirthDate.

我创建了一个名为TestUserStore一个内存中的用户存储

I've created an in-memory user store named TestUserStore

[TestMethod]
public void Register()
{
    // Arrange
    var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
    var controller = new AccountController(userManager);

    // This will setup a fake HttpContext using Moq
    controller.SetFakeControllerContext();

    // Act
    var result =
        controller.Register(new RegisterViewModel
        {
            BirthDate = TestBirthDate,
            UserName = TestUser,
            Password = TestUserPassword,
            ConfirmPassword = TestUserPassword
        }).Result;

    // Assert
    Assert.IsNotNull(result);

    var addedUser = userManager.FindByName(TestUser);
    Assert.IsNotNull(addedUser);
    Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}

该controller.Register方法​​是通过MVC5但参考我在这里包括它产生的样板code。

The controller.Register method is boilerplate code generated by MVC5 but for reference purposes I'm including it here.

// POST: /Account/Register
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser() { UserName = model.UserName, BirthDate = model.BirthDate };
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await SignInAsync(user, isPersistent: false);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            AddErrors(result);
        }
    }

    // If we got this far, something failed, redisplay form
    return View(model);
}

当我打电话注册,它调用SignInAsync这是将发生的麻烦。

When I call Register, it calls SignInAsync which is where the trouble will occur.

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

在最底层,样板code包括该珍闻

At the lowest layer, the boilerplate code includes this tidbit

private IAuthenticationManager AuthenticationManager
{
    get
    {
        return HttpContext.GetOwinContext().Authentication;
    }
}

这是其中problm的根发生。这调用GetOwinContext是一个扩展方法,我不能嘲笑,我无法用存根更换(当然,除非我改变样板code)。

This is where the root of the problm occurs. This call to GetOwinContext is an extension method which I cannot mock and I cannot replace with a stub (unless of course I change the boilerplate code).

当我运行这个测试我得到一个异常

When I run this test I get an exception

Test method MVCLabMigration.Tests.Controllers.AccountControllerTest.Register threw exception: 
System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at System.Web.HttpContextBaseExtensions.GetOwinEnvironment(HttpContextBase context)
at System.Web.HttpContextBaseExtensions.GetOwinContext(HttpContextBase context)
at MVCLabMigration.Controllers.AccountController.get_AuthenticationManager() in AccountController.cs: line 330
at MVCLabMigration.Controllers.AccountController.<SignInAsync>d__40.MoveNext() in AccountController.cs: line 336

在以前的版本中的ASP.NET MVC团队工作非常努力,使code测试。看来现在测试一个的AccountController不会很容易在表面上。我有一些选择。

In prior releases the ASP.NET MVC team worked very hard to make the code testable. It seems on the surface that now testing an AccountController is not going to be easy. I have some choices.

我可以


  1. 修改锅炉板code,使其不调用扩展方法,并在该水平这个问题处理

  1. Modify the boiler plate code so that it doesn't call an extension method and deal with this problem at that level

安装的管道OWin用于测试目的

Setup the OWin pipeline for testing purposes

避免编写测试code,它需要AuthN / AuthZ的基础设施(不是一个合理的选择)

Avoid writing testing code that requires the AuthN / AuthZ infrastructure (not a reasonable option)

我不知道哪条路更好。任一个可以解决这个问题。我的问题归结为这是最好的策略。

I'm not sure which road is better. Either one could solve this. My question boils down to which is the best strategy.

请注意:是的,我知道,我并不需要测试code,我没写。提供MVC5的的UserManager基础设施就是这样一块基础设施,但是如果我想编写一个验证我的修改ApplicationUser或code,可验证的行为取决于用户角色的话,我必须测试使用的UserManager测试。

Note: Yes, I know that I don't need to test code that I didn't write. The UserManager infrastructure provided MVC5 is such a piece of infrastructure BUT if I want to write tests that verify my modifications to ApplicationUser or code that verifies behavior that depends upon user roles then I must test using UserManager.

推荐答案

我回答我的问题,所以我可以得到来自社会各界的感觉,如果你认为这是一个很好的答案。

I'm answering my own question so I can get a sense from the community if you think this is a good answer.

第1步:修改生成的AccountController使用支持字段提供AuthenticationManager的属性setter

Step 1: Modify the generated AccountController to provide a property setter for the AuthenticationManager using a backing field.

// Add this private variable
private IAuthenticationManager _authnManager;

// Modified this from private to public and add the setter
public IAuthenticationManager AuthenticationManager
{
    get
    {
        if (_authnManager == null)
            _authnManager = HttpContext.GetOwinContext().Authentication;
        return _authnManager;
    }
    set { _authnManager = value; }
}

第二步:
修改单元测试增加一个模拟的Microsoft.OWin.IAuthenticationManager接口

Step 2: Modify the unit test to add a mock for the Microsoft.OWin.IAuthenticationManager interface

[TestMethod]
public void Register()
{
    // Arrange
    var userManager = new UserManager<ApplicationUser>(new TestUserStore<ApplicationUser>());
    var controller = new AccountController(userManager);
    controller.SetFakeControllerContext();

    // Modify the test to setup a mock IAuthenticationManager
    var mockAuthenticationManager = new Mock<IAuthenticationManager>();
    mockAuthenticationManager.Setup(am => am.SignOut());
    mockAuthenticationManager.Setup(am => am.SignIn());

    // Add it to the controller - this is why you have to make a public setter
    controller.AuthenticationManager = mockAuthenticationManager.Object;

    // Act
    var result =
        controller.Register(new RegisterViewModel
        {
            BirthDate = TestBirthDate,
            UserName = TestUser,
            Password = TestUserPassword,
            ConfirmPassword = TestUserPassword
        }).Result;

    // Assert
    Assert.IsNotNull(result);

    var addedUser = userManager.FindByName(TestUser);
    Assert.IsNotNull(addedUser);
    Assert.AreEqual(TestBirthDate, addedUser.BirthDate);
}

现在测试通过。

好主意?坏主意?

这篇关于单元测试ASP.NET应用MVC5的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆