1

I am having a hard time figuring out how to do unit testing in my MVC Web App. Although it is not a really hard concept to grasp, I was not able to find out whether I need a specific framework for this or respect some certain steps, given its multi-layered architecture. I would be very glad if you could give me some tips or code snippets so I could have an idea on what I should actually do. I will give you an example from my project:

PhaseController

    public IActionResult Create()
    {

        return View();
    }

    [HttpPost]
    public IActionResult Create(Phase phase)
    {
        int releaseId = (int)TempData["id"];
        string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            string sql = "CreatePhase";

            using (SqlCommand command = new SqlCommand(sql, connection))
            {
                command.CommandType = CommandType.StoredProcedure;

                // adding parameters
                SqlParameter parameter = new SqlParameter
                {
                    ParameterName = "@Name",
                    Value = phase.Name,
                    SqlDbType = SqlDbType.VarChar,
                    Size = 50
                };
                command.Parameters.Add(parameter);

                parameter = new SqlParameter
                {
                    ParameterName = "@ReleaseId",
                    Value = releaseId,
                    SqlDbType = SqlDbType.Int
                };
                command.Parameters.Add(parameter);

                connection.Open();
                command.ExecuteNonQuery();
                connection.Close();
            }
        }

        return RedirectToAction("Index", "Phase", new { id = releaseId });
    }

Phase Model

namespace Intersection.Models
{
    public class Phase
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int ReleaseId { get; set; }
       // public IEnumerable<Phase> phases { get; set; }
    }
}

Take these two as an example - how should my test class look like in order to properly test the Create() method (The POST one as well)?

EDIT:

Following @Nkosi's reply, I am adding a different method from another controller:

public IActionResult Create()
    {
        List<Intersection.Models.Environment> environmentList = new List<Intersection.Models.Environment>();

        string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            connection.Open();

            string sql = "ReadEnvironments";
            SqlCommand command = new SqlCommand(sql, connection);
            command.CommandType = CommandType.StoredProcedure;

            using (SqlDataReader dataReader = command.ExecuteReader())
            {
                while (dataReader.Read())
                {
                    Intersection.Models.Environment environment = new Intersection.Models.Environment();
                    environment.Id = Convert.ToInt32(dataReader["Id"]);
                    environment.Name = Convert.ToString(dataReader["Name"]);
                    environmentList.Add(environment);
                }
            }

            ViewBag.Environments = environmentList;
            return View();
        }
    }

    [HttpPost]
    public IActionResult Create(Application application)
    {
        string connectionString = Configuration["ConnectionStrings:DefaultConnection"];
        using (SqlConnection connection = new SqlConnection(connectionString))
        {
            string sql = "CreateApplication";

            using (SqlCommand command = new SqlCommand(sql, connection))
            {
                command.CommandType = CommandType.StoredProcedure;

                // adding parameters
                SqlParameter parameter = new SqlParameter
                {
                    ParameterName = "@Name",
                    Value = application.Name,
                    SqlDbType = SqlDbType.VarChar,
                    Size = 50
                };
                command.Parameters.Add(parameter);

                parameter = new SqlParameter
                {
                    ParameterName = "@Environment",
                    Value = application.Environment,
                    SqlDbType = SqlDbType.Int
                };
                command.Parameters.Add(parameter);

                connection.Open();
                command.ExecuteNonQuery();
                connection.Close();
            }
        }

        return RedirectToAction("Index", "Application");
    }
Questieme
  • 913
  • 2
  • 15
  • 34
  • 3
    It seems like you should learn the fundamentals of unit testing through a tutorial or something like that. StackOverflow really isn't the place for "where do I start?" type questions. My personal favorite tools for unit testing are NUnit and Moq for mocking. I'd look into those for a bit, make an attempt, and then come back with what you have. To properly test your methods, you need to test every possible input and dependency setups. This will be quite a few tests for each function to be comprehensive. –  Jan 08 '20 at 14:37
  • I have watched some tutorials and read some guides. As I said, I do understand the concept and how it works. I just don't know how to apply these things to my controller methods. Probably I might need to dive in and do some more research, but, honestly, I felt like I was always reaching a dead end - hence I posted the question here. I understand that it pretty much sounds like a "where do I start?" type of question. However, any help would be incredibly appreciated. – Questieme Jan 08 '20 at 14:41
  • 3
    @Questieme There is nothing to test really in the first method. The second one however is tightly coupled to implementation details that would make unit testing it in isolation difficult. Consider refactoring it to remove the tight coupling so that it is more flexible to isolated testing. – Nkosi Jan 08 '20 at 14:46
  • @Nkosi Thanks for the tip! However, I don't quite understand what you mean by `tightly coupled to implementation details`? I have added another method in the original post - could you tell me whether that one is also difficult to make testing in isolation or not? – Questieme Jan 08 '20 at 14:51
  • 1
    @Questieme Read up on SOLID principles. Specifically Single Responsibility Principle / Separation of Concerns, and Dependency Inversion Principle. – Nkosi Jan 08 '20 at 14:57
  • You can test that the first method returns the expected View and doesn't throw any exceptions. Many important unit tests feel redundant. –  Jan 08 '20 at 16:00

2 Answers2

1

In terms of testing your Controllers themselves, i.e. that they return a view and the correct data to the view, this is easy and covered in MSDN:

https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/unit-testing/creating-unit-tests-for-asp-net-mvc-applications-cs

For testing your own logic (i.e. the database call in your example), you would ideally first move that logic outside of the controller actions, so you are testing it in isolation, and not getting mixed up with the framework related stuff. You would then typically also separate the data access from the actual logic (although in your simple example, there isn't actually any logic to test, just data access) and then you can mock the returned data and ensure your logic does what it is expected to when presented with controlled sets of data.

Try reading some things like:

How should one unit test a .NET MVC controller?

Fat model / thin controller vs. Service layer

https://jonhilton.net/2016/05/23/3-ways-to-keep-your-asp-net-mvc-controllers-thin/

In the examples you have posted, you don't really have any logic - you are basically just doing database access, and assuming you trust the SqlCommand and stuff to work correctly, have nothing to really test... In this case you probably want an Integration Test instead, where you'd set up a blank database, call the create method, then query that it created the database rows you think it should have. But this is technically not really a 'unit' test.

Milney
  • 6,253
  • 2
  • 19
  • 33
  • Thanks a lot for your detailed answer. I finally got it and hopefully, the links you have provided will help me further. – Questieme Jan 08 '20 at 15:23
1

For Unit testing a controller, swagger and postman are really good tools.

Also, You should try testing for scenarios like 200 ok, not found and bad request.

For Service layer, I suggest NUnit testing.

For Nunit, you can try this link:

https://dzone.com/articles/how-to-write-unit-tests-for-a-net-core-application-1

If you are using Repository or Unit of work pattern, then using MOQ for mocking a repository is a good idea.

In unit testing if you have

A and B then creating truth table is a good way to go.

A | B

T | T true

T | F false

F | T false

F | F false

Keep your controller layer thin and follow solid design principles

Gauravsa
  • 6,330
  • 2
  • 21
  • 30