Use when writing, improving, or debugging unit tests that verify isolated functions, methods, and classes. Covers cross-platform frameworks including Vitest, Jest, xUnit, NUnit, MSTest, pytest, JUnit 5, and Go testing with mocking strategies, test doubles, AAA pattern, and naming conventions.
USE FOR: unit test frameworks, Vitest, Jest, xUnit, NUnit, pytest, JUnit, Go test, mocking, test doubles, AAA pattern
DO NOT USE FOR: multi-component tests (use integration-testing), browser tests (use e2e-testing), API contract verification (use contract-testing)
Use when writing, improving, or debugging unit tests that verify isolated functions, methods, and classes. Covers cross-platform frameworks including Vitest, Jest, xUnit, NUnit, MSTest, pytest, JUnit 5, and Go testing with mocking strategies, test doubles, AAA pattern, and naming conventions.
USE FOR: unit test frameworks, Vitest, Jest, xUnit, NUnit, pytest, JUnit, Go test, mocking, test doubles, AAA pattern
DO NOT USE FOR: multi-component tests (use integration-testing), browser tests (use e2e-testing), API contract verification (use contract-testing)
Unit Testing — Testing Isolated Functions and Classes
Overview
Unit tests verify the of an application in isolation. They are fast, cheap to run, and provide rapid feedback on whether individual functions, methods, and classes behave correctly. In the Test Trophy model, unit tests sit above static analysis and below integration tests.
smallest testable parts
When to unit test: Pure functions, business logic, algorithms, data transformations, edge cases, error handling, and any code with complex branching.
The AAA Pattern (Arrange-Act-Assert)
Every unit test should follow three distinct phases:
Arrange — Set up test data, dependencies, and preconditions
xUnit is the most popular .NET test framework — used by the .NET team itself.
using Xunit;
using Moq;
publicclassOrderServiceTests
{
privatereadonly Mock<IOrderRepository> _mockRepo;
privatereadonly Mock<IEmailService> _mockEmail;
privatereadonly OrderService _service;
publicOrderServiceTests()
{
_mockRepo = new Mock<IOrderRepository>();
_mockEmail = new Mock<IEmailService>();
_service = new OrderService(_mockRepo.Object, _mockEmail.Object);
}
[Fact]
publicasync Task PlaceOrder_ValidOrder_SavesAndSendsConfirmation()
{
// Arrangevar order = new Order { CustomerId = "C1", Total = 99.99m };
_mockRepo.Setup(r => r.SaveAsync(It.IsAny<Order>()))
.ReturnsAsync(order with { Id = "O1" });
// Actvar result = await _service.PlaceOrderAsync(order);
// Assert
Assert.Equal("O1", result.Id);
_mockRepo.Verify(r => r.SaveAsync(order), Times.Once);
_mockEmail.Verify(e => e.SendConfirmationAsync("C1", "O1"), Times.Once);
}
[Theory]
[InlineData(0)]
[InlineData(-1)]
[InlineData(-100)]
publicasync Task PlaceOrder_InvalidTotal_ThrowsArgumentException(decimal total)
{
var order = new Order { CustomerId = "C1", Total = total };
await Assert.ThrowsAsync<ArgumentException>(
() => _service.PlaceOrderAsync(order));
}
[Theory]
[MemberData(nameof(DiscountTestCases))]
publicvoidApplyDiscount_VariousInputs_ReturnsExpectedResult(decimal amount, string tier, decimal expected)
{
var result = _service.ApplyDiscount(amount, tier);
Assert.Equal(expected, result);
}
publicstatic IEnumerable<object[]> DiscountTestCases()
{
yieldreturnnewobject[] { 100m, "gold", 85m };
yieldreturnnewobject[] { 100m, "silver", 90m };
yieldreturnnewobject[] { 100m, "bronze", 95m };
yieldreturnnewobject[] { 100m, "none", 100m };
}
}
NUnit
using NUnit.Framework;
using Moq;
[TestFixture]
publicclassCalculatorTests
{
private Calculator _calculator;
[SetUp]
publicvoidSetUp()
{
_calculator = new Calculator();
}
[Test]
publicvoidAdd_TwoPositiveNumbers_ReturnsSum()
{
var result = _calculator.Add(2, 3);
Assert.That(result, Is.EqualTo(5));
}
[TestCase(0, 0, 0)]
[TestCase(1, -1, 0)]
[TestCase(-5, -3, -8)]
[TestCase(int.MaxValue, 0, int.MaxValue)]
publicvoidAdd_VariousInputs_ReturnsExpectedSum(int a, int b, int expected)
{
var result = _calculator.Add(a, b);
Assert.That(result, Is.EqualTo(expected));
}
[Test]
publicvoidDivide_ByZero_ThrowsDivideByZeroException()
{
Assert.Throws<DivideByZeroException>(() => _calculator.Divide(10, 0));
}
}
MSTest
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
publicclassStringHelperTests
{
[TestMethod]
publicvoidTruncate_LongString_TruncatesWithEllipsis()
{
var result = StringHelper.Truncate("Hello, World!", 5);
Assert.AreEqual("Hello...", result);
}
[DataTestMethod]
[DataRow("", 5, "")]
[DataRow("Hi", 5, "Hi")]
[DataRow("Hello, World!", 5, "Hello...")]
publicvoidTruncate_VariousInputs_ReturnsExpected(string input, int maxLength, string expected)
{
var result = StringHelper.Truncate(input, maxLength);
Assert.AreEqual(expected, result);
}
}
AutoFixture (C# test data generation)
using AutoFixture;
using AutoFixture.AutoMoq;
using Xunit;
publicclassUserServiceAutoTests
{
privatereadonly IFixture _fixture;
publicUserServiceAutoTests()
{
_fixture = new Fixture().Customize(new AutoMoqCustomization());
}
[Theory, AutoData]
publicasync Task GetUser_ExistingId_ReturnsUser(string userId)
{
// AutoFixture generates userId automaticallyvar expectedUser = _fixture.Build<User>()
.With(u => u.Id, userId)
.Create();
var mockRepo = _fixture.Freeze<Mock<IUserRepository>>();
mockRepo.Setup(r => r.FindByIdAsync(userId)).ReturnsAsync(expectedUser);
var service = _fixture.Create<UserService>();
var result = await service.GetUserAsync(userId);
Assert.Equal(expectedUser.Name, result.Name);
}
}
dotnet test
dotnet test --filter "FullyQualifiedName~OrderService"
dotnet test --collect:"XPlat Code Coverage"
dotnet test --logger "trx;LogFileName=results.trx"
Python
pytest
pytest is the de facto standard for Python testing — simple, powerful, and extensible.
# conftest.py — shared fixturesimport pytest
from unittest.mock import AsyncMock, MagicMock
from myapp.database import Database
from myapp.services import UserService
@pytest.fixturedefmock_db():
"""Create a mock database connection."""
db = MagicMock(spec=Database)
db.query = AsyncMock()
return db
@pytest.fixturedefuser_service(mock_db):
"""Create UserService with mocked dependencies."""return UserService(db=mock_db)
@pytest.fixturedefsample_user():
"""Create a sample user dict."""return {
"id": "user-123",
"name": "Alice",
"email": "alice@example.com",
"role": "admin",
}
# test_user_service.pyimport pytest
from myapp.services import UserService
from myapp.exceptions import UserNotFoundError, ValidationError
classTestUserService:
asyncdeftest_get_user_returns_user_when_found(self, user_service, mock_db, sample_user):
# Arrange
mock_db.query.return_value = sample_user
# Act
result = await user_service.get_user("user-123")
# Assertassert result["name"] == "Alice"
mock_db.query.assert_called_once_with("SELECT * FROM users WHERE id = %s", ("user-123",))
asyncdeftest_get_user_raises_when_not_found(self, user_service, mock_db):
mock_db.query.return_value = Nonewith pytest.raises(UserNotFoundError, match="User user-999 not found"):
await user_service.get_user("user-999")
@pytest.mark.parametrize("email,is_valid", [
("alice@example.com", True),
("bob@company.org", True),
("not-an-email", False),
("", False),
("@missing-local.com", False),
])deftest_validate_email(self, user_service, email, is_valid):
if is_valid:
assert user_service.validate_email(email) isTrueelse:
with pytest.raises(ValidationError):
user_service.validate_email(email)
go test ./...
go test ./... -v
go test ./... -count=1 # Disable test caching
go test ./... -cover
go test ./... -coverprofile=coverage.out
go tool cover -html=coverage.out # Open HTML coverage report
go test -run TestCalculateDiscount ./pricing