Introduction
ShopNest Order Service logic — discounts, stock checks, totals — belongs in unit tests. One hour writing tests saves ten hours debugging production orders at 2 AM. This lesson uses xUnit and Moq with Arrange-Act-Assert on real order scenarios.
After this article you will
- Write [Fact] and [Theory] tests with xUnit
- Mock dependencies with Moq Setup and Verify
- Test services, validators, and controllers in isolation
- Follow naming conventions and AAA pattern
- Understand code coverage basics and TDD intro
Prerequisites
- Article 53 — gRPC with ASP.NET Core
- ShopNest Order Service / API from prior modules
Concept deep-dive
Why unit test?
ROI: catch regressions before deploy; document expected behavior; enable fearless refactoring. Indian service companies increasingly expect xUnit in interviews.
// Naming: MethodName_Scenario_ExpectedBehavior
public class OrderServiceTests
{
[Fact]
public async Task CreateOrderAsync_EmptyCart_ThrowsValidationException()
{
// Arrange
var mockRepo = new Mock<IOrderRepository>();
var svc = new OrderService(mockRepo.Object, Mock.Of<ILogger<OrderService>>());
// Act & Assert
await Assert.ThrowsAsync<ValidationException>(() =>
svc.CreateOrderAsync(new CreateOrderDto { Lines = new() }, "user1"));
mockRepo.Verify(r => r.AddAsync(It.IsAny<Order>(), default), Times.Never);
}
[Theory]
[InlineData(0, false)]
[InlineData(1, true)]
[InlineData(100, true)]
public void IsValidQuantity_ReturnsExpected(int qty, bool expected)
{
Assert.Equal(expected, OrderRules.IsValidQuantity(qty));
}
}
Moq: Setup defines behavior; Verify asserts calls; ReturnsAsync for async methods.
Hands-on — ShopNest E-Commerce Order Service
- ShopNest.OrderService.Tests xUnit project.
- Tests: valid order, empty cart, insufficient stock, discount cap.
- Mock IProductRepository returns stock levels.
- Test FluentValidation CreateOrderValidator rules.
- dotnet test --collect:"XPlat Code Coverage".
[Fact]
public async Task CreateOrderAsync_ValidCart_ReturnsOrderId()
{
var mockProducts = new Mock<IProductRepository>();
mockProducts.Setup(p => p.GetStockAsync(1, default)).ReturnsAsync(10);
var mockOrders = new Mock<IOrderRepository>();
mockOrders.Setup(o => o.AddAsync(It.IsAny<Order>(), default))
.Callback<Order, CancellationToken>((order, _) => order.Id = Guid.NewGuid());
var svc = new OrderService(mockOrders.Object, mockProducts.Object);
var id = await svc.CreateOrderAsync(validDto, "cust-1");
Assert.NotEqual(Guid.Empty, id);
}
Common errors & best practices
- Testing implementation details (private methods) — test public behavior.
- Over-mocking — mock boundaries (repos), not value objects.
- Flaky tests depending on DateTime.Now — inject IClock or freeze time.
- No Assert on exception type — use ThrowsAsync with specific type.
Interview questions
Q: [Fact] vs [Theory]?
A: Fact single case; Theory runs multiple InlineData rows.
Q: Mock vs Stub?
A: Mock verifies interactions; stub only returns canned data.
Q: What to unit test?
A: Business logic in services — not EF or HTTP pipeline (integration tests).
Q: AAA pattern?
A: Arrange setup, Act execute, Assert verify outcome.
Summary
- Unit tests protect ShopNest order business rules
- Moq isolates services from database and HTTP
- Theory tests parameterized edge cases efficiently
- Coverage guides gaps — 80% on core domain is a common target
Previous: gRPC with ASP.NET Core
Next: Integration Testing
FAQ
NUnit vs xUnit?
xUnit is default for modern .NET; parallel by default.
Test controllers?
Prefer testing services; controller tests often thin integration tests.