End Manual Testing๐♂️, Welcome Automation!๐ Master .NET Unit Testing in Controller with FakeItEasy & XUnit!๐ฅ
We're diving into a crucial aspect of developing robust and reliable Web APIs: Unit Testing Controllers. We'll be using three powerful tools to achieve this—FakeItEasy, Fluent Assertions, and XUnit. Whether you're a seasoned developer or just getting started, this tutorial will equip you with the skills to write clean, efficient, and maintainable unit tests for your Web API controllers. So, let's get started and make testing fun and effective!
# What is Unit Testing?
Unit Testing is a software testing technique where individual components or units of a software are tested in isolation from the rest of the application. The primary goal of unit testing is to validate that each unit of the software performs as expected. This helps in identifying bugs early in the development cycle, making the software more robust and reducing the cost and effort required for later stages of testing.
# Benefits of Unit Testing:
1. Early Bug Detection: Identifies bugs early in the development process.
2. Simplifies Integration: Ensures that each unit works correctly before integrating them into the larger system.
3. Documentation: Serves as documentation for the code, explaining how each unit is expected to behave.
4. Refactoring Confidence: Gives developers confidence to refactor code without fear of introducing new bugs.
# What is Mocking?
Mocking is a technique used in unit testing to simulate the behavior of complex objects or external systems that a unit interacts with. By using mock objects, you can control the behavior of these dependencies, making it easier to test the unit in isolation.
# Benefits of Mocking:
1. Isolation: Tests the unit without relying on external systems, ensuring tests are fast and reliable.
2. Control: Allows you to simulate different scenarios and edge cases.
3. Focus: Ensures that tests focus on the behavior of the unit under test, rather than its dependencies.
# Unit Testing Patterns
1. System Under Test (SUT)
The System Under Test (SUT) refers to the specific component or unit that is being tested. In unit testing, the SUT is isolated from its dependencies to ensure that tests are focused on the behavior of the SUT itself.
2. Arrange, Act, Assert (AAA) Pattern
The Arrange, Act, Assert (AAA) pattern is a common structure for writing unit tests. It helps in organizing the test code in a clear and consistent manner.
a. Arrange: Set up the conditions for the test. This includes creating objects, initializing variables, and configuring mock dependencies.
b. Act: Perform the action that you want to test. This involves calling the method or function on the SUT.
c. Assert: Verify the outcome of the action. This involves checking that the expected result matches the actual result.
# Create Web API project with XUnit Project
# Install Packages to the Api
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="x.x.x" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="x.x.x" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="x.x.x"/>
# Create Model
public class User
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
}
# Create Application Db Context File
public class UserDbContext : DbContext
{
public UserDbContext(DbContextOptions<UserDbContext> options) : base(options)
{
}
public DbSet<User> Users { get; set; }
}
# Create your Repository in API
public interface IUserInterface
{
// CRUD
Task<bool> CreateAsync(User user);
Task<User> GetByIdAsync(int id);
Task<IEnumerable<User>> GetAllAsync();
Task<bool> UpdateAsync(User user);
Task<bool> DeleteAsync(int id);
}
public class UserRepository(UserDbContext context) : IUserInterface
{
public async Task<bool> CreateAsync(User user)
{
context!.Users.Add(user);
var result = await context.SaveChangesAsync();
return result > 0;
}
public async Task<bool> DeleteAsync(int id)
{
var getUser = await context.Users.FirstOrDefaultAsync(_ => _.Id == id);
if (getUser != null)
{
context.Users.Remove(getUser);
var result = await context.SaveChangesAsync();
return result > 0;
}
return false;
}
public async Task<IEnumerable<User>> GetAllAsync() => await context!.Users.ToListAsync();
public async Task<User> GetByIdAsync(int id)
=> await context!.Users!.FirstOrDefaultAsync(_ => _.Id == id);
public async Task<bool> UpdateAsync(User user)
{
var getUser = await context.Users.FirstOrDefaultAsync(_ => _.Id == user.Id);
if (getUser != null)
{
getUser.Name = user.Name;
getUser.Email = user.Email;
var result = await context.SaveChangesAsync();
return result > 0;
}
return false;
}
}
# Register in Program.cs file
builder.Services.AddDbContext<UserDbContext>
(o => o.UseSqlite("Data Source = UnitTestDb.db"));
builder.Services.AddScoped<IUserInterface, UserRepository>();
# Add Database Migration
add-migration First
update-database
# Create Controller
[ApiController]
[Route("[controller]")]
public class UserController(IUserInterface userInterface) : ControllerBase
{
// Create
[HttpPost("add")]
public async Task<IActionResult> Create(User user)
{
var result = await userInterface.CreateAsync(user);
if (result)
return CreatedAtAction(nameof(Create), new { id = user.Id }, user);
else
return BadRequest();
}
// Read All
[HttpGet("get")]
public async Task<IActionResult> GetUsers()
{
var users = await userInterface.GetAllAsync();
if (!users.Any())
return NotFound();
else
return Ok(users);
}
// Read single
[HttpGet("get-single/{id:int}")]
public async Task<IActionResult> GetUser(int id)
{
var user = await userInterface.GetByIdAsync(id);
if (user is null)
return NotFound();
else
return Ok(user);
}
// Update
[HttpPut("update")]
public async Task<IActionResult> UpdateUser(User user)
{
var result = await userInterface.UpdateAsync(user);
if (result)
return Ok();
else
return NotFound();
}
// Delete
[HttpDelete("delete/{id}")]
public async Task<IActionResult> DeleteUser(int id)
{
var result = await userInterface.DeleteAsync(id);
if (result)
return NoContent();
else
return NotFound();
}
}
# Install these Packages to the XUnit project
<PackageReference Include="FakeItEasy" Version="x.x.x" />
<PackageReference Include="FluentAssertions" Version="x.x.x" />
# Now Create Unit Test To The Controller
public class UserControllerTest
{
private readonly IUserInterface UserInterface;
private readonly UserController UserController;
public UserControllerTest()
{
// Set up Dependencies
this.UserInterface = A.Fake<IUserInterface>();
// SUT -> System Under Test
this.UserController = new UserController(UserInterface);
}
private static User CreateFakeUser() => A.Fake<User>();
//Create
// This method returns Created(success) | BadRequest(fails) action results
[Fact]
public async void UserController_Create_ReturnCreated()
{
// Arrange
var user = CreateFakeUser();
//Act
A.CallTo(() => UserInterface.CreateAsync(user)).Returns(true);
var result = (CreatedAtActionResult)await UserController.Create(user);
// Assert
result.StatusCode.Should().Be(201);
result.Should().NotBeNull();
}
// Read All
//This method returns Ok(success) | NotFound(fails) action results
[Fact]
public async void UserController_GetUsers_ReturnOk()
{
// Arrange
var users = A.Fake<List<User>>();
users.Add(new User() { Name = "TestController", Email = "Controller Email" });
//Act
A.CallTo(() => UserInterface.GetAllAsync()).Returns(users);
var result = (OkObjectResult)await UserController.GetUsers();
// Assert
result.StatusCode.Should().Be(StatusCodes.Status200OK);
result.Should().NotBeNull();
}
// Read Single
//This method returns Ok(success) | NotFound(fails) action results
[Theory]
[InlineData(1)]
public async void UserController_GetUser_ReturnOk(int id)
{
// Arrange
var user = CreateFakeUser();
user.Name = "TestController"; user.Email = "Controller Email"; user.Id = id;
//Act
A.CallTo(() => UserInterface.GetByIdAsync(id)).Returns(user);
var result = (OkObjectResult)await UserController.GetUser(id);
// Assert
result.StatusCode.Should().Be(StatusCodes.Status200OK);
result.Should().NotBeNull();
}
// Update
// This method returns Ok(success) | NotFound(fails) action results
[Fact]
public async void UserController_Update_ReturnOk()
{
// Arrange
var user = CreateFakeUser();
//Act
A.CallTo(() => UserInterface.UpdateAsync(user)).Returns(true);
var result = (OkResult)await UserController.UpdateUser(user);
// Assert
result.StatusCode.Should().Be(200);
result.Should().NotBeNull();
}
//Delete
// This method returns NoContent(success) | NotFound(fails) action results
[Fact]
public async void UserController_Delete_ReturnNoContent()
{
// Arrange
int userId = 1;
//Act
A.CallTo(() => UserInterface.DeleteAsync(userId)).Returns(true);
var result = (NoContentResult)await UserController.DeleteUser(userId);
// Assert
result.StatusCode.Should().Be(StatusCodes.Status204NoContent);
result.Should().NotBeNull();
}
}
# End Result
# Summary
I've walked you through the essentials of unit testing Web API controllers using XUnit, FakeItEasy, and Fluent Assertions. We started by setting up our environment and writing our first test, then moved on to more advanced topics like mocking dependencies and writing fluent assertions. By now, you should have a solid understanding of how to write clean, maintainable unit tests that ensure your Web API controllers are robust and reliable.
# Conclusion
Thank you for tuning in to this repository of Netcode-Hub! I hope you found it informative and engaging. Unit testing is a crucial part of modern software development, and with the tools we've covered today, you'll be well-equipped to write effective tests for your Web API controllers.
# Here's a follow-up section to encourage engagement and support for Netcode-Hub:
๐ Get in touch with Netcode-Hub! ๐ซ
1. GitHub: [Explore Repositories] ๐
2. Twitter: [Stay Updated] ๐ฆ
3. Facebook: [Connect Here]๐
4. LinkedIn: [Professional Network]๐
5. Email: [business.netcodehub@gmail.com] ๐ง
# ☕️ If you've found value in Netcode-Hub's work, consider supporting the channel with a coffee!
Comments
Post a Comment