[Theory] and [InlineData]
Learn parameterized tests with [Theory] and [InlineData].
[Theory] and [InlineData]
[Fact] checks a single scenario; but we want to try the same logic with different inputs. The [Theory] attribute marks a parameterized test method; the [InlineData(...)] attribute supplies each data row. The runner calls the method once per row, treating each as a separate test.
Example: [Theory] [InlineData(2, 3, 5)] [InlineData(0, 0, 0)] public void TestAdd(int a, int b, int expected). One method, two tests; no code duplication.
Designing parameterized tests
In a parameterized test, each [InlineData] row contains constants matching the method signature. The method body uses these parameters and makes a single Assert call. Adding a scenario then is just adding a row.
Tip: if rows need shared setup, put it in the constructor or a TestHelper class. Row data should be only input and expected result; complex objects come from MemberData or ClassData.
Exercise Write a [Theory] test for Calculator
Define FactAttribute, TheoryAttribute, InlineDataAttribute, and a static Assert class. The InlineDataAttribute constructor takes params object[]. Add Calculator.Add(int, int). Mark CalculatorTests.TestAdd with [Theory] and three [InlineData] rows: (2, 3, 5), (0, 0, 0), and (-1, 1, 0). The body calls Assert.Equal(expected, Calculator.Add(a, b)). Main calls the test with (2, 3, 5) and prints "Test passed: Theory Add(2,3)=5".
Show example solution
Attribute { } and class InlineDataAttribute : Attribute { public InlineDataAttribute(params object[] data) { } }. Write static class Assert { public static void Equal<T>(T e, T a) { if (!EqualityComparer<T>.Default.Equals(e, a)) throw new Exception(); } }. Write class Calculator { public static int Add(int a, int b) => a + b; }. Write class CalculatorTests { [Theory] [InlineData(2, 3, 5)] [InlineData(0, 0, 0)] [InlineData(-1, 1, 0)] public void TestAdd(int a, int b, int expected) { Assert.Equal(expected, Calculator.Add(a, b)); } }. In Main: var t = new CalculatorTests(); t.TestAdd(2, 3, 5); Console.WriteLine("Test passed: Theory Add(2,3)=5");
Example solution:
using System;
class FactAttribute : Attribute { }
class TheoryAttribute : Attribute { }
class InlineDataAttribute : Attribute
{
public InlineDataAttribute(params object[] data) { }
}
static class Assert
{
public static void Equal<T>(T expected, T actual)
{
if (!EqualityComparer<T>.Default.Equals(expected, actual))
throw new Exception($"Expected {expected}, got {actual}");
}
}
class Calculator
{
public static int Add(int a, int b) => a + b;
}
class CalculatorTests
{
[Theory]
[InlineData(2, 3, 5)]
[InlineData(0, 0, 0)]
[InlineData(-1, 1, 0)]
public void TestAdd(int a, int b, int expected)
{
Assert.Equal(expected, Calculator.Add(a, b));
}
}
class Program
{
static void Main()
{
var tests = new CalculatorTests();
tests.TestAdd(2, 3, 5);
Console.WriteLine("Test passed: Theory Add(2,3)=5");
}
}
Test yourself
-
What is the difference between [Theory] and [Fact]?
- [Theory] works only for async methods
- [Theory] runs slower
- [Theory] never needs [InlineData]
- [Theory] runs a parameterized test with multiple data sets
Show answer
[Theory] runs a parameterized test with multiple data sets — Correct. [Theory] + [InlineData] runs one method with multiple data rows.
-
Complete the InlineData row
[InlineData(____, 3, 5)] public void TestAdd(int a, int b, int expected) { Assert.Equal(expected, Calculator.Add(a, b)); }Show answer
[InlineData(2, 3, 5)] public void TestAdd(int a, int b, int expected) { Assert.Equal(expected, Calculator.Add(a, b)); } -
Match attributes to their roles
- [Fact]
- Single-scenario test
- [Theory]
- Parameterized test
- [InlineData]
- One data row
- Assert
- Validation