Overview
Use these best practices when developing unit tests.
Avoid Dependencies in Unit Tests
Use dependency injection and the Explicit Dependencies Principle.
Naming Tests
Tests should have a 3-part name: {MethodBeingTested}{ScenarioToTest}{ExpectedBehavior}. Consider:
[Fact]
public void Test_Single() { /* ... */ }
public void Add_SingleNumber_ReturnsSameNumber() { /* ... */ }
Arrange, Act, Assert
- Arrange the objects by creating them and setting them up as necessary.
- Act on an object.
- Assert that something is expected.
Consider:
[Fact]
public void Add_EmptyString_ReturnsZero()
{
// Arrange
var stringCalculator = new StringCalculator();
// Act
var actual = stringCalculator.Add("");
// Assert
Assert.Equal(0, actual);
}
Avoid Multiple ‘Act’s
One act per test makes it clear which act is failing and ensures the test is only focused on a single case.
Write Minimally Passing Tests
The input to the unit test should be the simplest possible input that verifies the expected behavior.
For example, use 0 as input to some method instead of some other number when 0 would verify the behavior of that method.
Avoid Magic Strings
Use constants instead.
[Fact]
public void Add_BigNumber_ThrowsException()
{
var stringCalculator = new StringCalculator();
Action actual = () => stringCalculator.Add("1001");
Assert.Throws<OverflowException>(actual);
}
[Fact]
void Add_MaximumSumResult_ThrowsOverflowException()
{
var stringCalculator = new StringCalculator();
const string MAXIMUM_RESULT = "1001";
Action actual = () => stringCalculator.Add(MAXIMUM_RESULT);
Assert.Throws<OverflowException>(actual);
}
Avoid Logic and Flow Control
Avoid if
, while
, for
, switch
, etc, to avoid introducing bugs in a unit test. If logic is required,
consider splitting up the test into multiple tests.
Prefer Helper Methods over Setup and Teardown Methods
This improves readability and avoids sharing state between tests.
private readonly StringCalculator stringCalculator;
public StringCalculatorTests()
{
stringCalculator = new StringCalculator();
}
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
// This instance of StringCalculator is from shared state in the test class:
var result = stringCalculator.Add("0,1");
Assert.Equal(1, result);
}
private StringCalculator CreateDefaultStringCalculator()
{
return new StringCalculator();
}
[Fact]
public void Add_TwoNumbers_ReturnsSumOfNumbers()
{
// Uses a helper method to create a StringCalculator:
var stringCalculator = CreateDefaultStringCalculator();
var actual = stringCalculator.Add("0,1");
Assert.Equal(1, actual);
}
Validate Private Methods by Unit Testing Public Methods
Since private methods are an implementation detail, the only result that matters is whether the public method that uses the private method validates.
Stub Static References
Consider the scenario where production code includes calls to static references (like DateTime.Now
):
public int GetDiscountedPrice(int price)
{
if (DateTime.Now.DayOfWeek == DayOfWeek.Tuesday)
return price / 2;
else
return price;
}
To solve for this, wrap the code that needs to be controlled in an interface and have the production code depend on that interface:
public interface IDateTimeProvider
{
DayOfWeek DayOfWeek();
}
public int GetDiscountedPrice(int price, IDateTimeProvider dateTimeProvider)
{
if (dateTimeProvider.DayOfWeek() == DayOfWeek.Tuesday)
return price / 2;
else
return price;
}
The test suite can now be created:
public void GetDiscountedPrice_NotTuesday_ReturnsFullPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Monday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(2, actual);
}
public void GetDiscountedPrice_OnTuesday_ReturnsHalfPrice()
{
var priceCalculator = new PriceCalculator();
var dateTimeProviderStub = new Mock<IDateTimeProvider>();
dateTimeProviderStub.Setup(dtp => dtp.DayOfWeek()).Returns(DayOfWeek.Tuesday);
var actual = priceCalculator.GetDiscountedPrice(2, dateTimeProviderStub);
Assert.Equals(1, actual);
}