Summary - Testing basics
Combine [Fact], [Theory] and Mock to write a calculator test.
Recap: testing basics
This group covered xUnit basics: [Fact] for a single scenario, [Theory] + [InlineData] for parameterized tests, Assert for validation (Equal, True, Throws), and Mock<T> to isolate external dependencies.
A good test has three properties: fast (thousands per second), independent (order does not matter), and readable (the failure reason is clear). Now we apply these in a combined example.
Exercise Combined test for StringUtils
Define FactAttribute and a static Assert class with Assert.Equal<T>, Assert.True(bool), and Assert.Throws<T>(Action). StringUtils.Capitalize(string) throws ArgumentException for empty input; otherwise it returns the text with the first letter uppercased. Write three [Fact] methods in StringUtilsTests: TestCapitalizesLower calls Assert.Equal("Merhaba", StringUtils.Capitalize("merhaba")); TestReturnsSameWhenCapital calls Assert.True(StringUtils.Capitalize("A") == "A"); TestEmptyThrows calls Assert.Throws<ArgumentException>(() => StringUtils.Capitalize("")). Main runs all three and prints "Test passed: Capitalize checks".
Show example solution
Attribute { }. Write static class Assert { public static void Equal<T>(T e, T a) { if (!EqualityComparer<T>.Default.Equals(e, a)) throw new Exception(); } public static void True(bool c) { if (!c) throw new Exception(); } public static void Throws<T>(Action a) where T : Exception { try { a(); } catch (T) { return; } throw new Exception(); } }. Write class StringUtils { public static string Capitalize(string text) { if (string.IsNullOrEmpty(text)) throw new ArgumentException(); return char.ToUpper(text[0]) + text.Substring(1); } }. Write class StringUtilsTests { [Fact] public void TestCapitalizesLower() { Assert.Equal("Merhaba", StringUtils.Capitalize("merhaba")); } [Fact] public void TestReturnsSameWhenCapital() { Assert.True(StringUtils.Capitalize("A") == "A"); } [Fact] public void TestEmptyThrows() { Assert.Throws<ArgumentException>(() => StringUtils.Capitalize("")); } }. In Main, call all three and Console.WriteLine("Test passed: Capitalize checks");
Example solution:
using System;
class FactAttribute : Attribute { }
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}");
}
public static void True(bool condition)
{
if (!condition) throw new Exception("Expected: true");
}
public static void Throws<T>(Action action) where T : Exception
{
try { action(); }
catch (T) { return; }
throw new Exception("Expected exception was not thrown");
}
}
class StringUtils
{
public static string Capitalize(string text)
{
if (string.IsNullOrEmpty(text)) throw new ArgumentException();
return char.ToUpper(text[0]) + text.Substring(1);
}
}
class StringUtilsTests
{
[Fact]
public void TestCapitalizesLower()
{
Assert.Equal("Merhaba", StringUtils.Capitalize("merhaba"));
}
[Fact]
public void TestReturnsSameWhenCapital()
{
Assert.True(StringUtils.Capitalize("A") == "A");
}
[Fact]
public void TestEmptyThrows()
{
Assert.Throws<ArgumentException>(() => StringUtils.Capitalize(""));
}
}
class Program
{
static void Main()
{
var tests = new StringUtilsTests();
tests.TestCapitalizesLower();
tests.TestReturnsSameWhenCapital();
tests.TestEmptyThrows();
Console.WriteLine("Test passed: Capitalize checks");
}
}
Exercise [Theory] for StringUtils.Repeat
Define FactAttribute, TheoryAttribute, InlineDataAttribute (with params object[] constructor), and a static Assert class. StringUtils.Repeat(string text, int count) concatenates text count times and returns it. Mark StringUtilsTests.TestRepeat with [Theory] and three [InlineData] rows: ("a", 3, "aaa"), ("ab", 2, "abab"), ("x", 1, "x"). The body calls Assert.Equal(expected, StringUtils.Repeat(text, count)). Main calls the test with ("a", 3, "aaa") and prints "Test passed: Theory Repeat(a,3)=aaa".
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 StringUtils { public static string Repeat(string text, int count) { string r = ""; for (int i = 0; i < count; i++) r += text; return r; } }. Write class StringUtilsTests { [Theory] [InlineData("a", 3, "aaa")] [InlineData("ab", 2, "abab")] [InlineData("x", 1, "x")] public void TestRepeat(string text, int count, string expected) { Assert.Equal(expected, StringUtils.Repeat(text, count)); } }. In Main: var t = new StringUtilsTests(); t.TestRepeat("a", 3, "aaa"); Console.WriteLine("Test passed: Theory Repeat(a,3)=aaa");
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 StringUtils
{
public static string Repeat(string text, int count)
{
string result = "";
for (int i = 0; i < count; i++) result += text;
return result;
}
}
class StringUtilsTests
{
[Theory]
[InlineData("a", 3, "aaa")]
[InlineData("ab", 2, "abab")]
[InlineData("x", 1, "x")]
public void TestRepeat(string text, int count, string expected)
{
Assert.Equal(expected, StringUtils.Repeat(text, count));
}
}
class Program
{
static void Main()
{
var tests = new StringUtilsTests();
tests.TestRepeat("a", 3, "aaa");
Console.WriteLine("Test passed: Theory Repeat(a,3)=aaa");
}
}
Test yourself
-
Match testing concepts
- [Fact]
- Single scenario
- [Theory]
- Parameterized test
- Assert.Throws
- Error path
- Mock<T>
- Fake dependency
-
Which attribute marks a parameterized test?
- [Fact]
- [Theory]
- [Setup]
- [Test]
Show answer
[Theory] — Correct. [Theory] + [InlineData] define a parameterized test.