3

我正在尝试使用带有身份验证项目的新 VS2013 默认 MVC 来了解单元测试。我要测试的第一件事是注册用户。(我知道我可能不需要对它进行单元测试,因为它已经是经过 MS 测试的代码,但我想用它来理解基础知识)。我还听说新的会员代码更“可测试”,所以我不需要创建自己的会员界面等......

我正在使用 NSubstitute 作为伪造框架。

查看 Account Controller -> Register() 异步方法

namespace WebApplication1.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController()
        : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
        {
        }

        public AccountController(UserManager<ApplicationUser> userManager)
        {
            UserManager = userManager;
        }

        public UserManager<ApplicationUser> UserManager { get; private set; }

        ...


       //
       // POST: /Account/Register
       [HttpPost]
       [AllowAnonymous]
       [ValidateAntiForgeryToken]
       public async Task<ActionResult> Register(RegisterViewModel model)
       {
           if (ModelState.IsValid)
           {
                var user = new ApplicationUser() { UserName = model.UserName };
                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);
       }

如果我想编写一个简单的测试(例如 Register_RegisterValidUser),我该怎么做?我知道我需要以某种方式替换 UserManager 但这对我不起作用:

var substitute = Substitute.For<UserManager<ApplicationUser>>();

我也知道我需要使用 Task.FromResult 绕过异步 Task<> 函数,但我不确定如何从 CreateAsync() 和 SigninAsync() 方法返回有效对象。

有人可以提供一些示例测试代码吗?非常感谢!

4

2 回答 2

1

考试指导

您可以对几个行为进行单元测试,但我只会向您展示单个单元测试的方向,该测试只是验证是否接收到正确的 RedirectToAction 值。例如,

return RedirectToAction("Index", "Home");

提高可测试性

在你提到的问题中

我还听说新的会员代码更“可测试”,所以我不需要创建自己的会员界面等。

虽然这是真的,但我会做一个小的调整,以便我们可以使您的实现更具可测试性。这样,您的单元测试可以完全专注于您正在进行的测试的特定行为。换句话说,我们可以让您的 SUT(被测系统)更具可测试性。

  await SignInAsync(user, isPersistent: false);

我相信 SignInAsync 是一种私有方法。此方法中应该有一些行为,您可能可以将其提取到一个单独的实现中,您可以将其注入到 SUT 中。我们可以称之为ISignInManager

public interface ISignInManager {
    Task SignInAsync(ApplicationUser user, bool isPersistent);
}

这样做的好处是,现在您可以注入 ISignInManager 的行为来执行登录任务,并且您的 SUT 变得更加可测试。您应该看到在您的单元测试中会有更少的模拟/存根,并使您的测试更容易编写和理解。

单元测试

您可以利用 MSTest 方法的新异步/等待用法。这简化了我们过去编写的复杂且不可靠的测试。

验证正确的重定向路由控制器/操作方法的单元测试可以编写如下。

    [TestMethod]
    public async Task Register_RegisterValidUser_EnsureRedirectToIndexActionHomeController()
    {
        // Arrange
        var userManagerStub = Substitute.For<IUserManager<ApplicationUser>>();
        var tcs = new TaskCompletionSource<IdentityResult>();
        tcs.SetResult(new IdentityResult(true));
        userManagerStub.CreateAsync(Arg.Any<ApplicationUser>(), Arg.Any<string>()).Returns(tcs.Task);

        var signInManagerStub = Substitute.For<ISignInManager>>();

        signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true));

        var sut = new AccountController(userManagerStub) { SignInManager = signInManagerStub.Object };

        // Act
        var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult;

        // Assert
        Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString());
        Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString());
    }

以上使用 NSubstitute 作为隔离框架,但如果有人对 Moq 版本感兴趣,请参阅下文。

    [TestMethod]
    public async Task Register_RegisterValidUser_EnsureRedirectToIndexHome()
    {
        // Arrange
        var userManagerStub = new Mock<IUserManager<ApplicationUser>>();
        var tcs = new TaskCompletionSource<IdentityResult>();
        tcs.SetResult(new IdentityResult(true));
        userManagerStub.Setup(s => s.CreateAsync(It.IsAny<ApplicationUser>(), It.IsAny<string>())).Returns(tcs.Task);

        var signInManagerStub = new Mock<ISignInManager>();
        signInManagerStub.Setup(s => s.SignInAsync(It.IsAny<ApplicationUser>(), It.IsAny<bool>())).Returns(Task.FromResult(true));

        var sut = new AccountController(userManagerStub.Object) {SignInManager = signInManagerStub.Object};

        // Act
        var result = await sut.Register(new RegisterViewModel() { Password = "fakePw" }) as RedirectToRouteResult;

        // Assert
        Assert.AreEqual<string>("Index", result.RouteValues["action"].ToString());
        Assert.AreEqual<string>("Home", result.RouteValues["controller"].ToString());
    }
于 2013-11-10T11:39:46.270 回答
1

要使用 NSubstitute 模拟用户管理器,您必须使用以下命令:

var userStore = Substitute.For<IUserStore<ApplicationUser>>();
var userManager = Substitute.For<UserManager<ApplicationUser>>(userStore);

现在您也可以伪造方法结果。例如:

userManager.FindByNameAsync(Arg.Any<string>()).Returns(Task.FromResult(new ApplicationUser()));

希望这对您有所帮助。

于 2014-11-07T14:43:33.503 回答