C# .NET 基本 CRUD 控制器路由的 MOQ 单元测试

MOQ unit Testing for C# .NET basic CRUD Controller Routing

我正在尝试为我的 CRUD 路由控制器设置基本的最小起订量测试。我们的应用程序相当小,我们想先建立基本测试,然后再进行更高级的测试(使用假货等)。

这是我当前的测试页:

    [TestClass()]
    public class AdminNotesTest
    {

        [TestMethod]
        public void CreatingOneNote()
        {
            var request = new Mock<HttpRequestBase>();
            request.Setup(r => r.HttpMethod).Returns("POST");
            var mockHttpContext = new Mock<HttpContextBase>();
            mockHttpContext.Setup(c => c.Request).Returns(request.Object);
            var controllerContext = new ControllerContext(mockHttpContext.Object, new RouteData(), new Mock<ControllerBase>().Object);

            var adminNoteController = new AdminNotesController();
            adminNoteController.ControllerContext = controllerContext;
            var result = adminNoteController.Create("89df3f2a-0c65-4552-906a-08bceabb1198");

            Assert.IsNotNull(result);
        }
        [TestMethod]
        public void DeletingNote()
        {
            var controller = new AdminNotesController();
        }
    }
}

在这里您将能够看到我正在尝试点击并创建注释的控制器方法。

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(AdminNote adminNote)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    adminNote.AdminKey = System.Web.HttpContext.Current.User.Identity.GetUserId();
                    adminNote.AdminName = System.Web.HttpContext.Current.User.Identity.GetUserName();
                    adminNote.CreateDate = DateTime.Now;
                    adminNote.ModifiedDate = DateTime.Now;

                    adminNote.ObjectState = ObjectState.Added;
                    _adminNoteService.Insert(adminNote);



                    return RedirectToAction("UserDetails", "Admin", new { UserKey = adminNote.UserKey });
                }
            }
            catch (Exception ex)
            {
                ControllerConstants.HandleException(ex);
                ViewBag.PopupMessage(string.Format("We're sorry but an error occurred. {0}", ex.Message));
            }

            return View(adminNote);
        } 

我知道,为了让我的创建方法起作用,我需要为该方法提供 AdminKey 和 AdminName。我不想为这些测试中的任何一个访问数据库,而且我已经读到这实际上是可能的我没有很多经验并且想知道是否有人可以在此过程中指导我什么是最好的方法来解决这个问题并提供此信息。

感谢所有帮助的人,我希望在这个问题之后我可以在单元测试方面做得更好。

我相信您想要实现的目标是获得围绕创建操作编写单元测试的方向。 而且您不想访问数据库。 你也希望这很简单,不想使用高级假货。

您可以在这里编写的测试很少。 以下是您可能想要考虑的一些场景。 一种。是否调用了 adminNoteService 上的 AdminService.Insert 方法。

b。 AdminService.Insert 方法是否使用预期参数调用

c。 RedirectToAction 方法 return 预期的结果类型。

d.抛出异常时是否已抛出正确的消息、异常类型等

e。当模型状态有效时验证某些调用。

您可以编写的测试很少,但下面是一个帮助您入门的示例。

我要做的第一件事是非常清楚您尝试进行单元测试的内容。您在测试方法名称中表达这一点的方式。 假设我们要定位 'c'

而不是像"CreatingOneNote"这样的测试方法,我更喜欢像下面这样的测试方法名称..

public void CreateAction_ModelStateIsValid_EnsureRedirectToActionContainsExpectedResult()

我将对您的 SUT/Controller(正在测试的系统)

进行以下更改
public class AdminsNotesController : Controller
{
    private readonly IAdminNoteService _adminNoteService;

    public AdminsNotesController(IAdminNoteService adminNoteService)
    {
        _adminNoteService = adminNoteService;
        FakeDateTimeDelegate = () => DateTime.Now;
    }

    public Func<DateTime> DateTimeDelegate { get; set; }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create(AdminNote adminNote)
    {
        try
        {
            if (ModelState.IsValid)
            {
                adminNote.AdminKey = this.ControllerContext.HttpContext
                .User.Identity.GetUserId();
                adminNote.AdminName = this.ControllerContext.HttpContext
                .User.Identity.GetUserName();
                adminNote.CreateDate = DateTimeDelegate();
                adminNote.ModifiedDate = DateTimeDelegate();

                adminNote.ObjectState = ObjectState.Added;
                _adminNoteService.Insert(adminNote);

                return RedirectToAction("UserDetails", "Admin", 
                new { UserKey = adminNote.UserKey });
            }
        }
        catch (Exception ex)
        {
            ControllerConstants.HandleException(ex);
            ViewBag.PopupMessage(string.Format
            ("We're sorry but an error occurred. {0}", ex.Message));
        }

        return View(adminNote);
    }
}

如您所见 一种。我不使用真正的系统日期时间,而是使用 DateTime Delegate。这允许我提供一个假的约会时间 在测试期间。在实际生产代码中,它将使用实际系统日期时间。

b。您可以使用 ControllerContext.HttpContext.User.Identity 而不是 HttpContext.Current..这将使您能够 stub 我们的 HttpConext 和 ControllerContext 然后是 User 和 Identity。请看下面的测试。

[TestClass]
public class AdminNotesControllerTests
{
    [TestMethod]
    public void CreateAction_ModelStateIsValid_EnsureRedirectToActionContainsExpectedRoutes()
    {
        // Arrange
        var fakeNote = new AdminNote();
        var stubService = new Mock<IAdminNoteService>();
        var sut = new AdminsNotesController(stubService.Object);

        var fakeHttpContext = new Mock<HttpContextBase>();
        var fakeIdentity = new GenericIdentity("User");
        var principal = new GenericPrincipal(fakeIdentity, null);

        fakeHttpContext.Setup(t => t.User).Returns(principal);
        var controllerContext = new Mock<ControllerContext>();
        controllerContext.Setup(t => t.HttpContext)
        .Returns(fakeHttpContext.Object);
        sut.ControllerContext = controllerContext.Object;
        sut.FakeDateTimeDelegate = () => new DateTime(2015, 01, 01);

        // Act
        var result = sut.Create(fakeNote) as RedirectToRouteResult;

        // Assert
        Assert.AreEqual(result.RouteValues["controller"], "Admin");
        Assert.AreEqual(result.RouteValues["action"], "UserDetails");
    }       
}

如果您熟悉 AutoMocking 等高级单元测试概念,则可以大大简化此测试。 但就目前而言,我相信这会为您指明正确的方向。