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

722 views Asked by At

I am trying to set basic MOQ testing for my CRUD routes controller. Our application is fairly small and we want to establish basic testing first before we move into more advance testing (using fakes and what not).

This is my current test page:

    [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();
        }
    }
}

Here you will be able to see my controller method I am trying to hit and create a note.

[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);
        } 

I know that in order for my create method to work I will need to provide the method with a AdminKey and an AdminName. I dont want to hit the database for any of these tests and I have read that this is actually possible I dont have a lot of experience and was wondering if someone could guide me in this process as to what would be the best way to approach this and provide this info.

Thanks for all the help guys and I do hope that after this question I can get better at unit testing.

1

There are 1 answers

0
Spock On BEST ANSWER

I believe what you trying to achieve is to get some direction on writing a Unit Test around Create Action. And you don't want to hit the database. You also want this to be simple and don't want to use advanced Fakes.

There are few tests you may write here. Below are some scenarios you may want to think. a. Whether the AdminService.Insert Method on the adminNoteService is called.

b. Whether the AdminService.Insert method called with expected parameters

c. RedirectToAction method return the expected type of result.

d. When an exception being thrown whether the exeception has been thrown with correct message, exception type etc.

e. Verify certain calls when the Model state is valid.

There are few tests you can write, but below is an example to get you started.

First thing I would do is to be very clear on what you trying to Unit Test. The way you express this in your test method name. Let's say we want to target the 'c'

Instead of a test method like "CreatingOneNote" I would prefer a test method name like below..

public void CreateAction_ModelStateIsValid_EnsureRedirectToActionContainsExpectedResult()

I would make the following changes to your SUT/Controller (System Under Test)

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);
    }
}

As you have noticed a. I don't use the real system date time, instead I use DateTime Delegate. This allows me to provide a fake date time during testing. In the real production code it will use real system date time.

b. Instead of useing HttpContext.Current.. you can use ControllerContext.HttpContext.User.Identity. This will allow you to stub our the HttpConext and ControllerContext then User and Identity. Please see the test below.

[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");
    }       
}

This test can be simplify a lot if you are familiar advanced Unit Testing concepts such as AutoMocking. But for the time being, I'm sure this will point you to the right direction.