10.0 What This Teaches
- Creating a test project with
dotnet new xunit [Fact]tests and xUnit's assertion library- Parameterized tests with
[Theory]and[InlineData] - Testing exceptions with
Assert.Throws - Shared setup with constructors and
IDisposable - Running tests from the command line and IDE
10.1 Project Setup
dotnet new xunit -o MyLib.Tests
cd MyLib.Tests
dotnet add reference ../MyLib/MyLib.csproj # reference the code under test
dotnet test # build and run all tests
xunit and xunit.runner.visualstudio packages
automatically. Tests run inside a separate process from the code under test.
10.2 [Fact] Tests
[Fact] is a test. It must be
in a public class. xUnit's Assert class provides typed assertions:
using Xunit;
public class MathTests
{
[Fact]
public void Add_TwoPositives_ReturnsSum()
{
int result = Calculator.Add(3, 4);
Assert.Equal(7, result);
}
[Fact]
public void Add_NegativeNumbers_ReturnsSum()
{
Assert.Equal(-2, Calculator.Add(-1, -1));
}
}
10.3 Common Assert Methods
Assert.Equal(expected, actual); // value equality
Assert.NotEqual(unexpected, actual);
Assert.True(condition);
Assert.False(condition);
Assert.Null(obj);
Assert.NotNull(obj);
Assert.Same(expected, actual); // reference equality
Assert.IsType<int>(obj);
Assert.Contains(item, collection);
Assert.Empty(collection);
Assert.InRange(value, low, high);
10.4 [Theory] and [InlineData]
[Theory] is a parameterized test. Each [InlineData]
attribute provides one set of arguments - the test runs once per attribute:
[Theory]
[InlineData(5, 0, 10, 5)] // within range
[InlineData(-3, 0, 10, 0)] // below min
[InlineData(15, 0, 10, 10)] // above max
[InlineData(0, 0, 10, 0)] // at min boundary
public void Clamp_ReturnsExpected(int value, int lo, int hi, int expected)
{
Assert.Equal(expected, Calculator.Clamp(value, lo, hi));
}
[MemberData] or [ClassData] when test data is
too large to fit inline or must be computed dynamically.
10.5 Testing Exceptions
[Fact]
public void Divide_ByZero_ThrowsArgumentException()
{
var ex = Assert.Throws<ArgumentException>(() => Calculator.Divide(10, 0));
Assert.Contains("zero", ex.Message);
}
[Fact]
public void Parse_InvalidInput_ThrowsFormatException()
{
Assert.Throws<FormatException>(() => int.Parse("abc"));
}
Assert.Throws<T> returns the exception so you can inspect
its message, inner exception, or other properties.
10.6 Shared Setup
IDisposable for teardown:
public class DatabaseTests : IDisposable
{
private readonly FakeDatabase _db;
public DatabaseTests()
{
_db = new FakeDatabase();
_db.Seed();
}
[Fact]
public void GetById_ExistingId_ReturnsRecord()
{
var record = _db.GetById(1);
Assert.NotNull(record);
}
public void Dispose() => _db.Close();
}
10.7 Example - All Together
// Testing - xUnit test suite for a StringUtils class.
using Xunit;
public class StringUtilsTests
{
[Theory]
[InlineData("racecar", true)]
[InlineData("hello", false)]
[InlineData("", true)]
[InlineData("a", true)]
public void IsPalindrome_ReturnsExpected(string input, bool expected)
{
Assert.Equal(expected, StringUtils.IsPalindrome(input));
}
[Theory]
[InlineData("hello world", "Hello World")]
[InlineData("", "")]
[InlineData("a b c", "A B C")]
public void TitleCase_CapitalizesEachWord(string input, string expected)
{
Assert.Equal(expected, StringUtils.TitleCase(input));
}
[Fact]
public void WordCount_NullInput_ThrowsArgumentNullException()
{
Assert.Throws<ArgumentNullException>(() => StringUtils.WordCount(null!));
}
}
10.8 Exercise
Exercise
- Create an
xunittest project and write[Fact]tests for aTemperatureclass that converts between Celsius and Fahrenheit. - Add
[Theory]/[InlineData]tests for edge cases: absolute zero (-273.15°C), water freezing (0°C), boiling (100°C). - Test that constructing a
Temperaturebelow absolute zero throwsArgumentOutOfRangeException.
10.9 Common Mistakes
Asserting with == instead of Assert.Equal
// Wrong - silently passes even when result is wrong
bool ok = result == expected;
// Correct - xUnit reports expected vs actual on failure
Assert.Equal(expected, result);
Testing implementation details instead of behavior
Comparing floats with Assert.Equal
Assert.Equal(3.14, Math.PI); // fails: not exactly equal
Assert.Equal(3.14, Math.PI, precision: 2); // passes: compare to 2 decimal places
10.10 Key Terms
| Term | Meaning |
|---|---|
| [Fact] | Attribute marking a parameterless test method |
| [Theory] | Attribute marking a parameterized test; requires data attributes |
| [InlineData] | Provides one set of arguments for a [Theory] |
| Assert.Equal | Fails the test if expected and actual are not equal |
| Assert.Throws<T> | Asserts that an action throws exception T; returns the exception |
| xUnit | Open-source .NET unit testing framework; used by most .NET OSS projects |
| dotnet test | CLI command that builds and runs all test projects in a solution |
| IDisposable | Interface used for per-test teardown in xUnit test classes |