CodeBlanks Logo Icon CodeBlanks

[Theory] and [InlineData]

C# course · Advanced · Lesson 70/83

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

  1. 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.

  2. 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)); }
  3. Match attributes to their roles

    [Fact]
    Single-scenario test
    [Theory]
    Parameterized test
    [InlineData]
    One data row
    Assert
    Validation

All lessons in Advanced