NUnit Test MVC Controller

We want to test the controller in an MVC application. We want to pass values in and be able to catch the response.

The response from our controller will tell us if the test has passed or failed. If you build the application well then all these tests should pass once they get the right response.

Previously On Serversncode

All source code for the project can be found here

Getting started.

To Recap our project

  • NUnit_Demo.Web
  • Nunit_Demo.Data
  • Nunit_Demo.Tests
  • Nunit_Demo.Web is the MVC application.

NunitDemo.Data is a class library that get's called by the HomeController in the NunitDemo.Web application. This pretends to talk to a database for the purpose of these samples.

Nunit_Demo.Tests is where the tests take place and run from.

To test an MVC Controller we need to add MVC to our Test project. We can do this in NuGet

PM> Install-Package Microsoft.AspNet.Mvc

for more information

Now to our test setup.

We are going to test saving a user. In our Nunit_Demo.Data we add a new class called User

public class User
{
    public string Email_VC { get; set; }
    public string FirstName_VC { get; set; }
    public string LastName_VC { get; set; }
    public int Number_IN { get; set; }
}

Next we add a new class called UserQueries

    public int InsertUser(string sFirstName, string sLastName, string sEmail, int iNumber)
    {
        int iUserID = 0;
        
        

        // Create our user 
        User _user = new User
        {
            Email_VC = sEmail,
            FirstName_VC = sFirstName,
            LastName_VC = sLastName,
            Number_IN = iNumber
        };
    
        // Add code here to save user to the database.
        iUserID = 1;

        return iUserID;
    }

    public Boolean DeleteUser(int iUserId)
    {
        Boolean bSuccess = false;

        // Here we add code to delete the user from the database.
        bSuccess = true;

        return bSuccess;
    }

Our UserQueries has two methods. InsertUser and DeleteUser as before to keep things clean I've pretended to hit a database of some kind.

Now into our Nunit_Demo.Web Project we open the HomeController and add a method called SaveUser

    //Save User method to demonstrate Unit testing of parameters
    public ActionResult SaveUser(string firstname, string lastname, string email, int number)
    {
        UserQueries UQ = new UserQueries();

        int iUserId = 0;
        Boolean bValid = true;

        // Validate User Input
        // Do we have a first name
        bValid = (firstname == "" ? false : true);

        // Do we have a last name
        bValid = (lastname == "" ? false : true);

        // Do we have an email
        bValid = (email == "" ? false : true);


        

        // If Valid Save User
        if (bValid)
        {
            iUserId = UQ.InsertUser(firstname, lastname, email, number);
        }


        if (iUserId == 0)
        {
            return Redirect("/saveuser?message=MESSAGEHERE");
        }
        else
        {
            return Redirect("/UserAdded");
        }

This is a simple ActionResult method for MVC. It takes the parameters and then uses a redirect to pass you onto the next page or return you back to the save user page.

In this case if the parameters are missing it will set bValid to false and send you into a redirect to saveuser with a message.

Testing a controller

So back into our Nunit_Demo.Tests

We create a class UserTest

[TestFixture]
public class UserTest

Our first test is the easy one, Create a test user and get the UserID from the response of the controller.

    [Test]
    public void CreateUserTest()
    {
        string sFirstName = "James";
        string sLastName = "Test";
        string sEmail = "test@serversncode.com";
        int iNumber = 7;
        Boolean bTestSuccess = false;
        string sMessage = "";


        HomeController _homeController = new HomeController();

        ActionResult result = _homeController.SaveUser(sFirstName, sLastName, sEmail, iNumber);

        var actResult = (RedirectResult)result;
        int iUserID = 0;

        // User Added successfully
        if (actResult.Url.StartsWith("/UserAdded?id="))
        {
            // We remove the URL and pull the ID for the new user we just created
            string sURL = actResult.Url.Replace("/UserAdded?id=", "");

            iUserID = Convert.ToInt32(sURL);

            // If the user ID not 0
            if (iUserID != 0)
            {
                bTestSuccess = true;
                sMessage = "User Saved ok";
            }
            else
            {
                // If UserId is 0 something went wrong and the User didn't save ok, 
                // So the test fails.
                bTestSuccess = false;
                sMessage = "User Save failed";
            }
        }
        else
        {
            // If the result URL is Save user
            string sURL = actResult.Url.Replace("/saveuser?message=", "");



            // The test has passed if this code is hit.
            bTestSuccess = true;
            sMessage = "";
        }


        // Test fails if bTestSuccess is false;
        Assert.IsTrue(bTestSuccess, sMessage);

    }

I have a bit of code here so lets work trough it.

        string sFirstName = "James";
        string sLastName = "Test";
        string sEmail = "test@serversncode.com";
        int iNumber = 7;
        Boolean bTestSuccess = false;
        string sMessage = "";

We setup our parameters and values for the testing.

        HomeController _homeController = new HomeController();

        ActionResult result = _homeController.SaveUser(sFirstName, sLastName, sEmail, iNumber);

We then call our Home Controller.

NOTE: there are a number of ways to do this but I like this way, it's clean and easy to maintain over time.

        var actResult = (RedirectResult)result;
        int iUserID = 0;

Our SaveUser method responds with an ActionResult. We know for this that we are getting a RedirectResult as a response so we can cast that into our actResult.

        if (actResult.Url.StartsWith("/UserAdded?id="))

We then check our actResult.URL method if it starts with /UserAdded we know that the user was added successfully. Because that is the response we are expecting.

            string sURL = actResult.Url.Replace("/UserAdded?id=", "");

            iUserID = Convert.ToInt32(sURL);

Here I simply strip the url of the expected characters and leave myself with an int that I can easily convert.

            if (iUserID != 0)
            {
                bTestSuccess = true;
                sMessage = "User Saved ok";
            }
            else
            {
                // If UserId is 0 something went wrong and the User didn't save ok, 
                // So the test fails.
                bTestSuccess = false;
                sMessage = "User Save failed";
            }

We check to see is the userID 0 or not. If it is 0 we know this test has failed because this test should have saved a user to the system.

If iUseID is not 0 then we have a successful test.

        else
        {
            // If the result URL is Save user
            string sURL = actResult.Url.Replace("/saveuser?message=", "");



            // The test has passed if this code is hit.
            bTestSuccess = true;
            sMessage = "";
        }

Our SaveUser controller can send two redirect responses. One is the saved. and the user is a saveuser with a message.

If in this case our controller responds with /saveuser the test has succeeded, because if something has gone wrong and it hasn't allowed the user to move on. The system failed but sent the user back to the first screen.

NOTE: This isn't always the case and you might consider this a fail but that's open for you to decide.

        Assert.IsTrue(bTestSuccess, sMessage);

Our test will pass if the bTestSuccess flag is true. It's a simple yes / no test.

Ok so lets make the test do a bit more. Our controller validates the user input so it checks to see if we pass in the firstname, lastname or email.

We create a new test and set the firstname and lastname parameters to ""

        string sFirstName = "";
        string sLastName = "";
        string sEmail = "test@serversncode.com";
        int iNumber = 7;
        Boolean bTestSuccess = false;
        string sMessage = "";


        HomeController _homeController = new HomeController();

        ActionResult result = _homeController.SaveUser(sFirstName, sLastName, sEmail, iNumber);

        var actResult = (RedirectResult)result;
        int iUserID = 0;

The top part of this test is the same as our last test.

        // User Added successfully
        if (actResult.Url.StartsWith("/UserAdded?id="))
        {
            // With this test we are not expecting to be here. 
            // If we are then something went wrong in our validation.
            bTestSuccess = false;
            sMessage = "User Save failed";
        }

This is were it gets interesting. Because of our validation in the controller we should not get here. Our redirect should not send the user added. If it does the test has failed.

        else
        {
            // If the result URL is Save user
            sMessage = actResult.Url.Replace("/saveuser?message=", "");

            // The test has passed if this code is hit.
            bTestSuccess = true;
        }

We should come here with a message of some kind. The test has passed. Our controller validated the parameters found the missing values and sent it back.

About these tests

These tests are very simple and I think in most cases tests should be simple. Like anything in programming we can make things as complicated or as simple as we want. Really it depends if you want to punish yourself in 4 weeks time. If you don't then keep it simple.

I will be going deeper into the rabbit hole of making tests more complex but overall if I can I keep my tests clean and simple.

By doing that and sticking to it the tests are easy to maintain as the code base grows it can be maintained easier. There is less reason to remove tests or ignore them, which happens when tests are too complex or over the top.