NUnit Testing in ASP.NET CORE MC

 Let’s implement NUnit unit testing for an Employee Management System in ASP.NET Core MVC, incorporating a service layer and repository layer, and setting it up entirely within Visual Studio 2022 (without CLI commands). 

I’ll provide a step-by-step explanation with source code, comments, proper naming conventions, coding standards, and test results for the use case involving EmployeeDepartmentProject, and Task

We’ll focus on what each part does, why it’s used, and how to configure it in Visual Studio 2022.


Step 1: Understand the Setup
What
  • Build an MVC project with a service layer (business logic), repository layer (data access), and a test project using NUnit in Visual Studio 2022.
Why
  • Service Layer: Encapsulates business logic (e.g., assigning tasks), testable independently of data access.
  • Repository Layer: Abstracts data operations (e.g., in-memory or database), making tests mockable.
  • NUnit: Validates functionality, ensuring the system works as expected (e.g., task assignment in an Employee Management System).
Use Case
  • Entities: Employee, Department, Project, Task.
  • Focus: Test task assignment and retrieval for employees.

Step 2: Create Projects in Visual Studio 2022
How
  1. Open Visual Studio 2022:
    • Launch VS 2022, choose "Create a new project."
  2. Create MVC Project:
    • Search for "ASP.NET Core Web App (Model-View-Controller)".
    • Name: EmployeeManagement.
    • Location: Choose a folder (e.g., C:\Projects).
    • Framework: .NET 8 (latest as of March 2025).
    • Click "Create."
  3. Add Test Project:
    • In Solution Explorer, right-click the solution → "Add" → "New Project".
    • Search for "NUnit Test Project (.NET Core)".
    • Name: EmployeeManagement.Tests.
    • Location: Same as above.
    • Framework: .NET 8.
    • Click "Create."
  4. Add Project Reference:
    • In Solution Explorer, right-click EmployeeManagement.Tests → "Add" → "Project Reference".
    • Check EmployeeManagement, click "OK."
Why
  • Visual Studio simplifies project creation with templates, avoiding CLI complexity.
  • Separate test project keeps production and test code isolated.

Step 3: Define Models
What
  • Define entity classes for the system.
How
  • In EmployeeManagement, right-click "Models" folder → "Add" → "Class".
  • Create each file:
  • EmployeeManagement/Models/Employee.cs:
    csharp
    // Represents an employee in the system
    public class Employee
    {
        public int EmployeeId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int DepartmentId { get; set; }
        public Department Department { get; set; }
    }
  • EmployeeManagement/Models/Department.cs:
    csharp
    // Represents a department in the organization
    public class Department
    {
        public int DepartmentId { get; set; }
        public string DepartmentName { get; set; }
    }
  • EmployeeManagement/Models/Project.cs:
    csharp
    // Represents a project that employees work on
    public class Project
    {
        public int ProjectId { get; set; }
        public string ProjectName { get; set; }
    }
  • EmployeeManagement/Models/Task.cs:
    csharp
    // Represents a task assigned to an employee in a project
    public class Task
    {
        public int TaskId { get; set; }
        public string TaskName { get; set; }
        public int EmployeeId { get; set; }
        public int ProjectId { get; set; }
        public DateTime StartTime { get; set; }
        public DateTime EndTime { get; set; }
    }
Why
  • Models define the system’s core entities, enabling testing of operations like task assignment.
Naming Convention
  • PascalCase for classes (Employee) and properties (TaskId).
  • Clear, domain-specific names.

Step 4: Implement Repository Layer
What
  • Create an interface and in-memory repository for data access.
How
  • In EmployeeManagement, add a "Repositories" folder → right-click → "Add" → "Interface".
  • Name: ITaskRepository.cs.
  • Add a class InMemoryTaskRepository.cs.
  • EmployeeManagement/Repositories/ITaskRepository.cs:
    csharp
    public interface ITaskRepository
    {
        void AddTask(Task task);
        List<Task> GetTasksByEmployeeId(int employeeId);
    }
  • EmployeeManagement/Repositories/InMemoryTaskRepository.cs:

    public class InMemoryTaskRepository : ITaskRepository
    {
        private readonly List<Task> _tasks = new List<Task>();
    
        // Adds a task to the in-memory store
        public void AddTask(Task task)
        {
            _tasks.Add(task);
        }
    
        // Retrieves tasks for a specific employee
        public List<Task> GetTasksByEmployeeId(int employeeId)
        {
            return _tasks.Where(t => t.EmployeeId == employeeId).ToList();
        }
    }
Why
  • Repository abstracts data access, allowing tests to use an in-memory implementation instead of a database.
  • Interface enables mocking or swapping implementations (e.g., SQL Server later).
Coding Standards
  • Interface prefixed with I (ITaskRepository).
  • Private field _tasks with underscore prefix.
  • Concise, focused methods.

Step 5: Implement Service Layer
What
  • Create a service to handle business logic, using the repository.
How
  • In EmployeeManagement, add a "Services" folder → right-click → "Add" → "Interface" (IEmployeeService.cs) and "Class" (EmployeeService.cs).
  • EmployeeManagement/Services/IEmployeeService.cs:

    public interface IEmployeeService
    {
        Task AssignTaskToEmployee(int employeeId, int projectId, string taskName, DateTime startTime, DateTime endTime);
        List<Task> GetTasksForEmployee(int employeeId);
    }
  • EmployeeManagement/Services/EmployeeService.cs:

    public class EmployeeService : IEmployeeService
    {
        private readonly ITaskRepository _taskRepository;
    
        // Constructor injection for repository
        public EmployeeService(ITaskRepository taskRepository)
        {
            _taskRepository = taskRepository ?? throw new ArgumentNullException(nameof(taskRepository));
        }
    
        // Assigns a task to an employee for a project
        public Task AssignTaskToEmployee(int employeeId, int projectId, string taskName, DateTime startTime, DateTime endTime)
        {
            if (string.IsNullOrWhiteSpace(taskName))
                throw new ArgumentException("Task name cannot be empty.", nameof(taskName));
            if (endTime <= startTime)
                throw new ArgumentException("End time must be after start time.", nameof(endTime));
    
            var task = new Task
            {
                TaskId = GetNextTaskId(), // Simplified for in-memory
                EmployeeId = employeeId,
                ProjectId = projectId,
                TaskName = taskName,
                StartTime = startTime,
                EndTime = endTime
            };
            _taskRepository.AddTask(task);
            return task;
        }
    
        // Retrieves all tasks for a given employee
        public List<Task> GetTasksForEmployee(int employeeId)
        {
            return _taskRepository.GetTasksByEmployeeId(employeeId);
        }
    
        // Helper method to simulate auto-incrementing ID
        private int GetNextTaskId()
        {
            var tasks = _taskRepository.GetTasksByEmployeeId(0); // All tasks
            return tasks.Count > 0 ? tasks.Max(t => t.TaskId) + 1 : 1;
        }
    }
Why
  • Service layer contains business rules (e.g., validation), testable independently of controllers or UI.
  • Dependency injection (ITaskRepository) promotes loose coupling.
Coding Standards
  • Constructor validation for dependencies.
  • Descriptive method names (AssignTaskToEmployee).
  • Consistent spacing and bracing.

Step 6: Configure Dependency Injection
What
  • Register services in the MVC project.
How
  • Open EmployeeManagement/Program.cs:

    var builder = WebApplication.CreateBuilder(args);
    builder.Services.AddControllersWithViews();
    
    // Register dependencies
    builder.Services.AddScoped<ITaskRepository, InMemoryTaskRepository>();
    builder.Services.AddScoped<IEmployeeService, EmployeeService>();
    
    var app = builder.Build();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseCookiePolicy();
    app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
    app.Run();
Why
  • Dependency injection ensures testable, modular code by providing instances at runtime.

Step 7: Write NUnit Tests
What
  • Create tests for EmployeeService using NUnit in Visual Studio 2022.
How
  • In EmployeeManagement.Tests, rename the default UnitTest1.cs to EmployeeServiceTests.cs (right-click → "Rename").
  • EmployeeManagement.Tests/EmployeeServiceTests.cs:

    using NUnit.Framework;
    using EmployeeManagement.Services;
    using EmployeeManagement.Models;
    using EmployeeManagement.Repositories;
    using System;
    using System.Collections.Generic;
    
    namespace EmployeeManagement.Tests
    {
        [TestFixture]
        public class EmployeeServiceTests
        {
            private IEmployeeService _employeeService;
            private ITaskRepository _taskRepository;
    
            // Sets up test dependencies before each test
            [SetUp]
            public void Setup()
            {
                _taskRepository = new InMemoryTaskRepository();
                _employeeService = new EmployeeService(_taskRepository);
            }
    
            [Test]
            public void AssignTaskToEmployee_ValidInput_ReturnsAssignedTask()
            {
                // Arrange
                int employeeId = 1;
                int projectId = 1;
                string taskName = "Design Homepage";
                DateTime startTime = new DateTime(2025, 3, 1, 9, 0, 0);
                DateTime endTime = new DateTime(2025, 3, 1, 17, 0, 0);
    
                // Act
                var result = _employeeService.AssignTaskToEmployee(employeeId, projectId, taskName, startTime, endTime);
    
                // Assert
                Assert.IsNotNull(result, "Task should not be null.");
                Assert.AreEqual(employeeId, result.EmployeeId, "EmployeeId should match.");
                Assert.AreEqual(projectId, result.ProjectId, "ProjectId should match.");
                Assert.AreEqual(taskName, result.TaskName, "TaskName should match.");
            }
    
            [Test]
            public void AssignTaskToEmployee_InvalidTime_ThrowsArgumentException()
            {
                // Arrange
                int employeeId = 1;
                int projectId = 1;
                string taskName = "Invalid Task";
                DateTime startTime = new DateTime(2025, 3, 1, 17, 0, 0);
                DateTime endTime = new DateTime(2025, 3, 1, 9, 0, 0); // End before start
    
                // Act & Assert
                var exception = Assert.Throws<ArgumentException>(() =>
                    _employeeService.AssignTaskToEmployee(employeeId, projectId, taskName, startTime, endTime));
                Assert.AreEqual("End time must be after start time.", exception.Message);
            }
    
            [Test]
            public void GetTasksForEmployee_WithTasks_ReturnsCorrectTasks()
            {
                // Arrange
                int employeeId = 2;
                _employeeService.AssignTaskToEmployee(employeeId, 1, "Task 1", DateTime.Now, DateTime.Now.AddHours(1));
                _employeeService.AssignTaskToEmployee(employeeId, 2, "Task 2", DateTime.Now, DateTime.Now.AddHours(2));
                _employeeService.AssignTaskToEmployee(3, 1, "Task 3", DateTime.Now, DateTime.Now.AddHours(1));
    
                // Act
                var tasks = _employeeService.GetTasksForEmployee(employeeId);
    
                // Assert
                Assert.AreEqual(2, tasks.Count, "Should return 2 tasks.");
                Assert.IsTrue(tasks.All(t => t.EmployeeId == employeeId), "All tasks should belong to the employee.");
            }
    
            [Test]
            public void GetTasksForEmployee_NoTasks_ReturnsEmptyList()
            {
                // Arrange
                int employeeId = 4;
    
                // Act
                var tasks = _employeeService.GetTasksForEmployee(employeeId);
    
                // Assert
                Assert.IsEmpty(tasks, "Should return an empty list.");
            }
        }
    }
Why
  • Tests verify EmployeeService logic through the repository, ensuring correct task management.
  • Setup ensures isolation per test.
Naming Convention
  • Class: EmployeeServiceTests (service name + “Tests”).
  • Methods: [Action]_[Condition]_[ExpectedResult] (e.g., GetTasksForEmployee_WithTasks_ReturnsCorrectTasks).
Coding Standards
  • AAA pattern (Arrange, Act, Assert).
  • Descriptive comments at key points.
  • Consistent indentation and spacing.

Step 8: Run Tests in Visual Studio 2022
How
  1. Open Test Explorer:
    • Go to "Test" → "Test Explorer" in the VS 2022 menu.
  2. Build Solution:
    • Press Ctrl+Shift+B or "Build" → "Build Solution."
  3. Run Tests:
    • In Test Explorer, right-click EmployeeServiceTests → "Run" (or click the green "Run All" button).
Test Results
  • Output (assuming all pass):
    Passed: AssignTaskToEmployee_ValidInput_ReturnsAssignedTask
    Passed: AssignTaskToEmployee_InvalidTime_ThrowsArgumentException
    Passed: GetTasksForEmployee_WithTasks_ReturnsCorrectTasks
    Passed: GetTasksForEmployee_NoTasks_ReturnsEmptyList
    Total: 4, Passed: 4, Failed: 0, Skipped: 0
  • Details:
    • ValidInput: Task assigned correctly.
    • InvalidTime: Exception thrown as expected.
    • WithTasks: Returns 2 tasks for employee 2.
    • NoTasks: Returns empty list.
Why
  • Visual Studio’s Test Explorer provides a GUI for running and debugging tests, ideal for development workflows.

Step 9: Test Cases Summary
Test Case
Description
Input
Expected Output
Result
AssignTaskToEmployee_ValidInput_ReturnsAssignedTask
Assign a valid task
EmpId=1, ProjId=1, "Design", valid times
Task with correct properties
Pass
AssignTaskToEmployee_InvalidTime_ThrowsArgumentException
Assign task with invalid time
EmpId=1, ProjId=1, "Task", end < start
ArgumentException: "End time..."
Pass
GetTasksForEmployee_WithTasks_ReturnsCorrectTasks
Retrieve tasks for employee with tasks
EmpId=2 (2 tasks added)
List with 2 tasks
Pass
GetTasksForEmployee_NoTasks_ReturnsEmptyList
Retrieve tasks for employee with no tasks
EmpId=4 (no tasks)
Empty list
Pass

Conclusion
  • What: Added service and repository layers, tested with NUnit in Visual Studio 2022.
  • Why: Ensures modular, testable code for the Employee Management System.
  • Setup: Done via VS 2022 GUI, avoiding CLI for your preference.
This setup is ready for expansion (e.g., SQL repository, more tests).

Comments

Popular posts from this blog

FREE Webinar: Run Your Own Independent DeepSeek LLM

Interview Tips: Dot NET Framework vs Net CORE

Delegates and Events