Writes comprehensive unit tests using xUnit and Moq. Use when the user wants to create tests, add test coverage, write unit tests, or test new functionality. Always finds root cause of failures and fixes the code, not the tests.
Creates comprehensive unit tests in src/Tests using xUnit and Moq for mocking.
❌ Forbidden without deep justification:
[Fact(Skip = "...")] to hide failures✅ Always:
If src/Tests project doesn't exist:
dotnet new xunit -n Tests -o src/Tests
cd src/Tests
dotnet add package Moq
dotnet add package FluentAssertions # Optional but recommended
dotnet add reference ../dev/{ProjectName}
src/Tests/
├── Tests.csproj
├── {Feature}/
│ ├── {Class}Tests.cs
│ └── {Class}MockSetup.cs # If complex mocking needed
└── TestHelpers/
├── TestFixtures.cs
└── MockFactories.cs
{MethodName}_{Scenario}_{ExpectedResult}
Examples:
GetUser_WithValidId_ReturnsUserGetUser_WithInvalidId_ThrowsNotFoundExceptionCalculate_WithNegativeInput_ReturnsZero[Fact]
public void MethodName_Scenario_ExpectedResult()
{
// Arrange
var mockService = new Mock<IService>();
mockService.Setup(x => x.GetData()).Returns(expectedData);
var sut = new MyClass(mockService.Object);
// Act
var result = sut.MethodUnderTest();
// Assert
Assert.Equal(expected, result);
mockService.Verify(x => x.GetData(), Times.Once);
}
// Basic mock
var mock = new Mock<IRepository>();
mock.Setup(x => x.GetById(It.IsAny<int>())).Returns(entity);
// Async methods
mock.Setup(x => x.GetAsync(id)).ReturnsAsync(entity);
// Throwing exceptions
mock.Setup(x => x.Save(null)).Throws<ArgumentNullException>();
// Verifying calls
mock.Verify(x => x.Save(It.IsAny<Entity>()), Times.Once);
// Callback for inspection
mock.Setup(x => x.Save(It.IsAny<Entity>()))
.Callback<Entity>(e => capturedEntity = e);
| Priority | What | Coverage Goal |
|---|---|---|
| 1 | Public methods | 100% |
| 2 | Edge cases | All identified |
| 3 | Error paths | All exceptions |
| 4 | Boundary conditions | Min/max/null |
| 5 | Business logic | 100% |
For each public method, test:
dotnet test --filter "FullyQualifiedName~FailingTestName" -v detailed
Read the full error message and stack trace.
Is the test correct?
Is the code correct?
Debug if needed
// ❌ WRONG - Changing test to pass
[Fact]
public void GetUser_WithInvalidId_ReturnsNull() // Changed from ThrowsException
{
var result = sut.GetUser(-1);
Assert.Null(result); // Weakened assertion
}
// ✅ RIGHT - Fix the source code
// In UserService.cs:
public User GetUser(int id)
{
if (id <= 0)
throw new ArgumentException("Invalid user ID", nameof(id));
// ... rest of implementation
}
Only change a test if:
Required justification format:
/// <summary>
/// JUSTIFICATION FOR TEST CHANGE:
/// - Original: Expected ArgumentException for negative values
/// - Changed to: Returns null for negative values
/// - Reason: Product decision - graceful degradation preferred
/// - Approved by: [stakeholder]
/// - Date: [date]
/// </summary>
[Fact]
public void GetUser_WithNegativeId_ReturnsNull()
Read the source file and identify:
search_nodes → Find testing patterns for similar code
query-docs → Get xUnit/Moq best practices if needed
Create test file mirroring source structure:
src/dev/Services/UserService.cs → src/Tests/Services/UserServiceTests.csdotnet test src/Tests/
If tests fail:
using Moq;
using Xunit;
namespace UnitTests.Services;
public class UserServiceTests
{
private readonly Mock<IUserRepository> _mockRepo;
private readonly Mock<ILogger<UserService>> _mockLogger;
private readonly UserService _sut;
public UserServiceTests()
{
_mockRepo = new Mock<IUserRepository>();
_mockLogger = new Mock<ILogger<UserService>>();
_sut = new UserService(_mockRepo.Object, _mockLogger.Object);
}
[Fact]
public async Task GetUserAsync_WithValidId_ReturnsUser()
{
// Arrange
var expectedUser = new User { Id = 1, Name = "Test" };
_mockRepo.Setup(x => x.GetByIdAsync(1))
.ReturnsAsync(expectedUser);
// Act
var result = await _sut.GetUserAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal(expectedUser.Id, result.Id);
}
[Fact]
public async Task GetUserAsync_WithInvalidId_ThrowsArgumentException()
{
// Arrange & Act & Assert
await Assert.ThrowsAsync<ArgumentException>(
() => _sut.GetUserAsync(-1));
}
[Fact]
public async Task GetUserAsync_WhenNotFound_ThrowsNotFoundException()
{
// Arrange
_mockRepo.Setup(x => x.GetByIdAsync(999))
.ReturnsAsync((User)null);
// Act & Assert
await Assert.ThrowsAsync<NotFoundException>(
() => _sut.GetUserAsync(999));
}
}
For common mocking patterns, see moq-patterns.md