Skip to content
dotnet-testing-unit-test-fundamentals logo

.NET Unit Test Fundamentals Guide

dotnet-testing-unit-test-fundamentals

.NET unit test fundamentals and FIRST principles specialized skill. Used when creating unit tests, understanding testing basics, learning 3A Pattern, and mastering testing best practices. Covers FIRST principles, AAA Pattern, Fact/Theory, testing pyramid, etc. Keywords: unit test, unit test, uni...

rudironsoni/Synaxis0installs2stars

SKILL.md

Full skill instructions

Source: kevintsengtw/​dotnet-testing-agent-skills (MIT). Ported into dotnet-agent-harness.

.NET Unit Test Fundamentals Guide

Applicable Scenarios

Use this skill when asked to perform the following tasks:

  • Create unit tests for .NET classes or methods
  • Review or improve existing test quality
  • Design test cases following FIRST principles
  • Explain test naming conventions and best practices
  • Write tests using xUnit

FIRST Principles

Every unit test must conform to the following principles:

F - Fast

Test execution time should be in milliseconds, not dependent on external resources.

[Fact] // Fast: no external dependencies, executes quickly
public void Add_WithInput1And2_ShouldReturn3()
{
    // Pure memory operations, no I/​O or network latency
    var calculator = new Calculator();
    var result = calculator.Add(1, 2);
    Assert.Equal(3, result);
}
```text

### I - Independent

Tests should not have dependencies, each test creates a new instance.

```csharp
[Fact] // Independent: each test creates a new instance
public void Increment_StartingFrom0_ShouldReturn1()
{
    var counter = new Counter(); // Each test creates a new instance, not affected by other tests
    counter.Increment();
    Assert.Equal(1, counter.Value);
}
```text

### R - Repeatable

Should produce the same results in any environment, not dependent on external state.

```csharp
[Fact] // Repeatable: same results every execution
public void Increment_ExecutedMultipleTimes_ShouldProduceConsistentResults()
{
    var counter = new Counter();
    counter.Increment();
    counter.Increment();
    counter.Increment();

    // This test produces the same result every execution
    Assert.Equal(3, counter.Value);
}
```text

### S - Self-Validating

Test results should be clearly pass or fail, using clear assertions.

```csharp
[Fact] // Self-Validating: clear validation
public void IsValidEmail_WithValidInput_ShouldReturnTrue()
{
    var emailHelper = new EmailHelper();
    var result = emailHelper.IsValidEmail("[email protected]");

    Assert.True(result); // Clear pass or fail
}
```text

### T - Timely

Tests should be written before or simultaneously with production code, ensuring code testability.

## 3A Pattern Structure

Every test method **must** follow the Arrange-Act-Assert pattern:

```csharp
[Fact]
public void Add_WithNegativeAndPositiveNumbers_ShouldReturnCorrectResult()
{
    // Arrange - prepare test data and dependencies
    var calculator = new Calculator();
    const int a = -5;
    const int b = 3;
    const int expected = -2;

    // Act - execute the method under test
    var result = calculator.Add(a, b);

    // Assert - verify results match expectations
    Assert.Equal(expected, result);
}
```text

### Block Responsibilities

| Block        | Responsibility                           | Notes                            |
| ----------- | ------------------------------ | ----------------------------------- |
| **Arrange** | Prepare objects, data, and Mocks needed for testing | Use `const` to declare constant values, improve readability |
| **Act**     | Execute the method under test               | Usually only one line, calling the method under test          |
| **Assert**  | Verify results                       | Each test only validates one behavior              |

## Test Naming Conventions

Use the following format to name test methods:

```text
[MethodUnderTest]_[TestScenario]_[ExpectedBehavior]
```text

### Naming Examples

| Method Name                                       | Description         |
| ---------------------------------------------- | ------------ |
| `Add_WithInput1And2_ShouldReturn3`                         | Test normal input |
| `Add_WithNegativeAndPositiveNumbers_ShouldReturnCorrectResult`            | Test boundary conditions |
| `Divide_WithInput10And0_ShouldThrowDivideByZeroException` | Test exception case |
| `IsValidEmail_WithNullInput_ShouldReturnFalse`          | Test invalid input |
| `GetDomain_WithValidEmail_ShouldReturnDomainName`       | Test return value   |

> 💡 **Tip**: Using Chinese naming can make test reports more readable, especially during team communication.

## xUnit Test Attributes

### [Fact] - Single Test Case

Used for testing a single scenario:

```csharp
[Fact]
public void Add_WithInput0And0_ShouldReturn0()
{
    var calculator = new Calculator();
    var result = calculator.Add(0, 0);
    Assert.Equal(0, result);
}
```text

### [Theory] + [InlineData] - Parameterized Tests

Used for testing multiple input combinations:

```csharp
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
[InlineData(100, -50, 50)]
public void Add_WithVariousNumberCombinations_ShouldReturnCorrectResult(int a, int b, int expected)
{
    var calculator = new Calculator();
    var result = calculator.Add(a, b);
    Assert.Equal(expected, result);
}
```text

### Testing Multiple Invalid Inputs

```csharp
[Theory]
[InlineData("invalid-email")]
[InlineData("@example.com")]
[InlineData("test@")]
[InlineData("test.example.com")]
public void IsValidEmail_WithInvalidEmailFormats_ShouldReturnFalse(string invalidEmail)
{
    var emailHelper = new EmailHelper();
    var result = emailHelper.IsValidEmail(invalidEmail);
    Assert.False(result);
}
```text

## Exception Testing

Test expected exception throwing scenarios:

```csharp
[Fact]
public void Divide_WithInput10And0_ShouldThrowDivideByZeroException()
{
    // Arrange
    var calculator = new Calculator();
    const decimal dividend = 10m;
    const decimal divisor = 0m;

    // Act & Assert
    var exception = Assert.Throws<DivideByZeroException>(
        () => calculator.Divide(dividend, divisor)
    );

    // Verify exception message
    Assert.Equal("Divisor cannot be zero", exception.Message);
}
```text

## Test Project Structure

Recommended project structure:

```text
Solution/
├── src/
│   └── MyProject/
│       ├── Calculator.cs
│       └── MyProject.csproj
└── tests/
    └── MyProject.Tests/
        ├── CalculatorTests.cs
        └── MyProject.Tests.csproj
```text

## Test Project Template (.csproj)

```xml
<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net9.0</​TargetFramework>
        <ImplicitUsings>enable</​ImplicitUsings>
        <Nullable>enable</​Nullable>
        <IsPackable>false</​IsPackable>
    </​PropertyGroup>

    <ItemGroup>
        <PackageReference Include="coverlet.collector" Version="6.0.4">
            <PrivateAssets>all</​PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</​IncludeAssets>
        </​PackageReference>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
        <PackageReference Include="xunit" Version="2.9.3" />
        <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
            <PrivateAssets>all</​PrivateAssets>
            <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</​IncludeAssets>
        </​PackageReference>
    </​ItemGroup>

    <ItemGroup>
        <Using Include="Xunit" />
    </​ItemGroup>

    <ItemGroup>
        <ProjectReference Include="..\..\src\MyProject\MyProject.csproj" />
    </​ItemGroup>

</​Project>
```text

## Common Assertion Methods

| Assertion Method                            | Purpose             |
| ----------------------------------- | ---------------- |
| `Assert.Equal(expected, actual)`    | Verify equality         |
| `Assert.NotEqual(expected, actual)` | Verify inequality       |
| `Assert.True(condition)`            | Verify condition is true     |
| `Assert.False(condition)`           | Verify condition is false     |
| `Assert.Null(object)`               | Verify is null      |
| `Assert.NotNull(object)`            | Verify is not null    |
| `Assert.Throws<T>(action)`          | Verify throws specific exception |
| `Assert.Empty(collection)`          | Verify collection is empty     |
| `Assert.Contains(item, collection)` | Verify collection contains item |

## Test Generation Checklist

When generating tests for a method, ensure coverage of:

- [ ] **Happy Path** - Standard input produces expected output
- [ ] **Boundary Conditions** - Minimum, maximum values, zero, empty strings
- [ ] **Invalid Input** - null, negative numbers, wrong formats
- [ ] **Exception Cases** - Scenarios expected to throw exceptions

## Reference Resources

### Original Articles

This skill content is extracted from "Old School Software Engineer's Testing Practice - 30 Day Challenge" series:

- **Day 01 - Old School Engineer's Testing Enlightenment**
  - Ironman Article: https://ithelp.ithome.com.tw/articles/10373888
  - Sample Code: https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day01

More skills from rudironsoni

dotnet-project-analysis logo
rudironsoni/Synaxis

dotnet-project-analysis

Analyzes .NET solution layout and build config -- .sln, .csproj, CPM.

2 0
View
wiki-agents-md logo
rudironsoni/Synaxis

wiki-agents-md

Generates AGENTS.md files for coding agent context

2 0
View
wiki-architect logo
rudironsoni/Synaxis

wiki-architect

Analyzes repos and generates structured catalogues

2 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View