### Simplifying Setup Logic with Helper Methods Source: https://tunit.dev/docs/guides/best-practices Demonstrates a best practice for keeping setup logic clean by extracting complex initialization into private helper methods. This improves readability and maintainability of test hooks. ```csharp // ✅ Good: Simple setup with extracted helpers [Before(Test)] public async Task Setup() { _database = await CreateTestDatabase(); _testUser = await CreateTestUser(); } private async Task CreateTestDatabase() { var db = await TestDatabase.CreateAsync(); await db.SeedDefaultData(); return db; } ``` -------------------------------- ### Less Flexible Shared Setup Using Class Hooks Source: https://tunit.dev/docs/guides/best-practices Shows a less flexible approach to sharing setup logic using [Before(Class)] and [After(Class)] hooks. This method relies on static fields and is less adaptable than using ClassDataSource. ```csharp // ❌ Less flexible: Using hooks for shared setup public class ApiTests { private static WebApplicationFactory? _factory; [Before(Class)] public static async Task StartServer() { _factory = new WebApplicationFactory(); } [After(Class)] public static async Task StopServer() { _factory?.Dispose(); } // Tests use static _factory field } ``` -------------------------------- ### Implement Async Initialization for Test Classes in C# Source: https://tunit.dev/docs/advanced/extension-points Provides an example of implementing the IAsyncInitializer interface to perform asynchronous setup operations before tests in a class are executed. This ensures resources like database connections are ready. ```csharp public interface IAsyncInitializer { Task InitializeAsync(); } public class DatabaseTests : IAsyncInitializer { private DatabaseConnection _connection; public async Task InitializeAsync() { _connection = await DatabaseConnection.CreateAsync(); await _connection.MigrateAsync(); } [Test] public async Task TestDatabaseOperation() { // _connection is guaranteed to be initialized await _connection.ExecuteAsync("SELECT 1"); } } ``` -------------------------------- ### Async Property Initialization Example (C#) Source: https://tunit.dev/docs/test-lifecycle/property-injection Demonstrates how to implement IAsyncInitializer and IAsyncDisposable for asynchronous setup and cleanup of properties within a test class. The InitializeAsync method performs delayed setup, and DisposeAsync handles cleanup. ```csharp using TUnit.Core; namespace MyTestProject; public class AsyncPropertyExample : IAsyncInitializer, IAsyncDisposable { public bool IsInitialized { get; private set; } public string? ConnectionString { get; private set; } public async Task InitializeAsync() { await Task.Delay(10); // Simulate async setup ConnectionString = "Server=localhost;Database=test"; IsInitialized = true; } public async ValueTask DisposeAsync() { await Task.Delay(1); // Cleanup IsInitialized = false; ConnectionString = null; } } ``` -------------------------------- ### TUnit Equality and Comparison Assertions Source: https://tunit.dev/docs/assertions/getting-started Provides examples of TUnit assertions for checking equality, inequality, greater than, less than or equal to, and range comparisons between values. ```csharp await Assert.That(actual).IsEqualTo(expected); await Assert.That(value).IsNotEqualTo(other); await Assert.That(score).IsGreaterThan(70); await Assert.That(age).IsLessThanOrEqualTo(100); await Assert.That(temperature).IsBetween(20, 30); ``` -------------------------------- ### Install TUnit Templates and Create Project Source: https://tunit.dev/docs/getting-started/installation This snippet shows the commands to install the TUnit templates and then create a new TUnit project using those templates. It assumes the .NET SDK is already installed. ```bash dotnet new install TUnit.Templates dotnet new TUnit -n "YourProjectName" ``` -------------------------------- ### NUnit Dynamic Test Fixture Setup Source: https://tunit.dev/docs/comparison/framework-differences Illustrates setting up NUnit test fixtures with dynamically injected data, such as tenants. This example shows how to use `TestFixtureSource` and a constructor for dependency injection. ```csharp [TestFixtureSource(typeof(Tenant), nameof(Tenant.AllTenants))] public class MyTests(Tenant tenant) { [Test] public async Task Test1() { ... } } ``` -------------------------------- ### TUnit Resource Management: Shared vs. Per-Test Setup Source: https://tunit.dev/docs/test-lifecycle/setup Contrasts inefficient per-test resource creation with efficient class-level setup for expensive resources like HttpClient. Uses [Before(Test)] for individual test setup and [Before(Class)] / [After(Class)] for shared setup and cleanup. ```csharp public class ApiTests { private HttpClient _client; // ❌ Creates new client for EVERY test [Before(Test)] public void Setup() { _client = new HttpClient { BaseAddress = new Uri("https://api.example.com") }; } [Test] public async Task Test1() { /* ... */ } [Test] public async Task Test2() { /* ... */ } // Client created 2 times unnecessarily } ``` ```csharp public class ApiTests { private static HttpClient _client; // ✅ Creates client once for all tests [Before(Class)] public static void SetupOnce() { _client = new HttpClient { BaseAddress = new Uri("https://api.example.com") }; } [After(Class)] public static void CleanupOnce() { _client?.Dispose(); } [Test] public async Task Test1() { /* ... */ } [Test] public async Task Test2() { /* ... */ } // Client created only once } ``` -------------------------------- ### Write a Basic Test in C# with TUnit Source: https://tunit.dev/docs/index This snippet demonstrates how to write a basic asynchronous test case using the TUnit framework. It includes arranging the test subject, performing an action, and asserting the expected outcome. This is a fundamental example for getting started with TUnit. ```csharp [Test] public async Task MyTest() { // Arrange var calculator = new Calculator(); // Act var result = calculator.Add(2, 3); // Assert await Assert.That(result).IsEqualTo(5); } ``` -------------------------------- ### Correct vs. Incorrect Awaiting of Assertions Source: https://tunit.dev/docs/assertions/getting-started Illustrates the correct usage of `await` with TUnit assertions, highlighting why it's necessary for async support and rich error messages. The incorrect example shows a common mistake that TUnit's analyzer will flag. ```csharp // ✅ Correct - awaited await Assert.That(result).IsEqualTo(42); // ❌ Wrong - will cause compiler warning Assert.That(result).IsEqualTo(42); ``` -------------------------------- ### Complete TUnit Test Example with Usings Source: https://tunit.dev/docs/comparison/framework-differences Provides a complete example of a TUnit test case, including explicit using statements for TUnit.Assertions and TUnit.Core. This demonstrates the `[Test]` attribute and the assertion syntax. ```C# using TUnit.Assertions; using TUnit.Assertions.Extensions; using TUnit.Core; namespace MyTests; public class ValidatorTests // No [TestClass] needed! { [Test] // Only this attribute is required public async Task IsPositive_WithNegativeNumber_ReturnsFalse() { var result = Validator.IsPositive(-1); await Assert.That(result).IsFalse(); } } ``` -------------------------------- ### Async Initialization in ConfigureTestServices Source: https://tunit.dev/docs/examples/aspnet This C# example shows how to handle asynchronous operations during test setup. It performs async work in `SetupAsync` and uses the results in the synchronous `ConfigureTestServices` method by storing the result in a member variable. ```csharp public class MyTest : TestsBase { private string _authToken = null!; protected override async Task SetupAsync() { // Async work here _authToken = await GetAuthTokenAsync(); } protected override void ConfigureTestServices(IServiceCollection services) { // Use the result from SetupAsync services.AddSingleton(new AuthConfig { Token = _authToken }); } } ``` -------------------------------- ### Example TUnit Test File Structure Source: https://tunit.dev/docs/getting-started/installation This C# code illustrates a minimal TUnit test file. It highlights how global usings provided by the TUnit package eliminate the need for explicit 'using' statements for common TUnit namespaces like TUnit.Core and TUnit.Assertions. ```csharp namespace MyTests; public class MyTests { [Test] public async Task MyTest() { await Assert.That(true).IsTrue(); } } ``` -------------------------------- ### Setup and Cleanup Database Tests with TUnit Hooks Source: https://tunit.dev/docs/guides/best-practices Demonstrates using [Before(Test)] and [After(Test)] hooks to set up and tear down a test database for each test method. This ensures a clean state for every test. ```csharp public class DatabaseTests { private TestDatabase? _database; [Before(Test)] public async Task SetupDatabase() { _database = await TestDatabase.CreateAsync(); } [After(Test)] public async Task CleanupDatabase() { if (_database != null) await _database.DisposeAsync(); } [Test] public async Task Can_insert_record() { // Database is ready to use await _database!.InsertAsync(new Record { Id = 1 }); var result = await _database.GetAsync(1); await Assert.That(result).IsNotNull(); } } ``` -------------------------------- ### Utilize Base Classes for Common Setup in Tunit (C#) Source: https://tunit.dev/docs/examples/aspnet Illustrates the use of base classes in Tunit for organizing common test setup logic. It shows a generic `TestsBase`, a specialized `DatabaseTestBase` with schema creation, and an example `UserTests` class inheriting from `DatabaseTestBase`. ```csharp public abstract class TestsBase : WebApplicationTest { } public abstract class DatabaseTestBase : TestsBase { protected override async Task SetupAsync() { await CreateSchemaAsync(); } } public class UserTests : DatabaseTestBase { [Test] public async Task CreateUser_Works() { /* ... */ } } ``` -------------------------------- ### TUnit Equivalent Test Class Example Source: https://tunit.dev/docs/migration/xunit The TUnit equivalent of the xUnit test class, showcasing TUnit's syntax for fixture injection via primary constructors, setup using the [Before] attribute, parameterized tests with [Arguments] and [MethodDataSource], and fluent assertions. It highlights TUnit's async-by-default nature and strongly-typed data sources. ```csharp [ClassDataSource(Shared = SharedType.PerClass)] public class UserServiceTests(DatabaseFixture dbFixture) { private UserService _userService = null!; [Before(Test)] public async Task Setup() { _userService = new UserService(dbFixture.Connection); await _userService.InitializeAsync(); } [Test] [Arguments("john@example.com", "John")] [Arguments("jane@example.com", "Jane")] public async Task CreateUser_WithValidData_Succeeds(string email, string name, TestContext context) { context.OutputWriter.WriteLine($"Creating user: {name}"); var user = await _userService.CreateUserAsync(email, name); await Assert.That(user).IsNotNull(); await Assert.That(user.Email).IsEqualTo(email); await Assert.That(user.Name).IsEqualTo(name); context.OutputWriter.WriteLine($"User created with ID: {user.Id}"); } [Test] public async Task GetUser_WhenNotFound_ThrowsException() { await Assert.ThrowsAsync( () => _userService.GetUserAsync(99999)); } [Test] [MethodDataSource(nameof(GetInvalidEmails))] public async Task CreateUser_WithInvalidEmail_ThrowsException(string invalidEmail) { await Assert.ThrowsAsync( () => _userService.CreateUserAsync(invalidEmail, "Test")); } public static IEnumerable GetInvalidEmails() { yield return ""; yield return "not-an-email"; yield return "@example.com"; } } ``` -------------------------------- ### Matrix Test Example (C#) Source: https://tunit.dev/docs/benchmarks/methodology Shows an example of matrix testing, which generates multiple test combinations based on specified parameters. This is useful for comprehensive coverage of different scenarios. ```csharp [Test] [Matrix("Create", "Update", "Delete")] // Operation [Matrix("User", "Admin", "Guest")] // Role public void TestPermissions(string op, string role) { // 9 test combinations } ``` -------------------------------- ### Install TUnit Package Source: https://tunit.dev/docs/index This command shows how to add the TUnit testing framework to your .NET project using the dotnet CLI. This is the first step to start using TUnit for your testing needs. ```bash dotnet add package TUnit ``` -------------------------------- ### Benchmark Execution Example (C#) Source: https://tunit.dev/docs/benchmarks/methodology Demonstrates how to define a benchmark method in C# using the BenchmarkDotNet library. This specific example shows executing an external executable with arguments. ```csharp [Benchmark] public async Task TUnit() { await Cli.Wrap("UnifiedTests.exe") .WithArguments(["--filter", "TestCategory"]) .ExecuteBufferedAsync(); } ``` -------------------------------- ### xUnit Test Class Example Source: https://tunit.dev/docs/migration/xunit A complete xUnit test class demonstrating user service testing with fixtures, asynchronous initialization, parameterized tests using Theory and InlineData, and exception assertions. It utilizes IClassFixture for dependency injection and IAsyncLifetime for setup and teardown. ```csharp public class UserServiceTests : IClassFixture, IAsyncLifetime { private readonly DatabaseFixture _dbFixture; private readonly ITestOutputHelper _output; private UserService _userService = null!; public UserServiceTests(DatabaseFixture dbFixture, ITestOutputHelper output) { _dbFixture = dbFixture; _output = output; } public async Task InitializeAsync() { _userService = new UserService(_dbFixture.Connection); await _userService.InitializeAsync(); } public Task DisposeAsync() => Task.CompletedTask; [Theory] [InlineData("john@example.com", "John")] [InlineData("jane@example.com", "Jane")] public async Task CreateUser_WithValidData_Succeeds(string email, string name) { _output.WriteLine($"Creating user: {name}"); var user = await _userService.CreateUserAsync(email, name); Assert.NotNull(user); Assert.Equal(email, user.Email); Assert.Equal(name, user.Name); _output.WriteLine($"User created with ID: {user.Id}"); } [Fact] public async Task GetUser_WhenNotFound_ThrowsException() { await Assert.ThrowsAsync( () => _userService.GetUserAsync(99999)); } [Theory] [MemberData(nameof(GetInvalidEmails))] public async Task CreateUser_WithInvalidEmail_ThrowsException(string invalidEmail) { await Assert.ThrowsAsync( () => _userService.CreateUserAsync(invalidEmail, "Test")); } public static IEnumerable GetInvalidEmails() { yield return new object[] { "" }; yield return new object[] { "not-an-email" }; yield return new object[] { "@example.com" }; } } ``` -------------------------------- ### Install TUnit.FsCheck NuGet Package Source: https://tunit.dev/docs/examples/fscheck This command installs the TUnit.FsCheck NuGet package, which provides the necessary integration for using FsCheck with TUnit. ```bash dotnet add package TUnit.FsCheck ``` -------------------------------- ### Complete Test Class Example: NUnit to TUnit Source: https://tunit.dev/docs/migration/nunit This snippet shows a full test class written in NUnit and its equivalent in TUnit. It covers the mapping of attributes like [TestFixture], [OneTimeSetUp], [SetUp], [TearDown], [OneTimeTearDown], [Test], [TestCase], and [TestCaseSource] to their TUnit counterparts ([Before(Class)], [Before(Test)], [After(Test)], [After(Class)], [Test], [Arguments], [MethodDataSource]). It also highlights changes in assertion syntax and the introduction of async support in TUnit. ```csharp [TestFixture] public class ProductServiceTests { private IDatabase _database; private ProductService _productService; [OneTimeSetUp] public void OneTimeSetup() { // Runs once before all tests in the class _database = new InMemoryDatabase(); _database.Initialize(); } [SetUp] public void Setup() { // Runs before each test _productService = new ProductService(_database); } [Test] [Category("Unit")] [TestCase("Widget", 10.99)] [TestCase("Gadget", 25.50)] public void CreateProduct_WithValidData_Succeeds(string name, decimal price) { var product = _productService.CreateProduct(name, price); Assert.That(product, Is.Not.Null); Assert.That(product.Name, Is.EqualTo(name)); Assert.That(product.Price, Is.EqualTo(price)); } [Test] [Category("Unit")] public void GetProduct_WhenNotFound_ReturnsNull() { var product = _productService.GetProduct(999); Assert.That(product, Is.Null); } [Test] [TestCaseSource(nameof(InvalidProductData))] public void CreateProduct_WithInvalidData_ThrowsException(string name, decimal price) { Assert.Throws(() => _productService.CreateProduct(name, price)); } private static IEnumerable InvalidProductData() { yield return new object[] { "", 10.00 }; yield return new object[] { "Product", -5.00 }; yield return new object[] { null, 10.00 }; } [TearDown] public void TearDown() { // Runs after each test _productService?.Dispose(); } [OneTimeTearDown] public void OneTimeTearDown() { // Runs once after all tests in the class _database?.Dispose(); } } ``` ```csharp public class ProductServiceTests { private IDatabase _database = null!; private ProductService _productService = null!; [Before(Class)] public async Task ClassSetup() { // Runs once before all tests in the class _database = new InMemoryDatabase(); await _database.InitializeAsync(); } [Before(Test)] public async Task Setup() { // Runs before each test _productService = new ProductService(_database); } [Test] [Property("Category", "Unit")] [Arguments("Widget", 10.99)] [Arguments("Gadget", 25.50)] public async Task CreateProduct_WithValidData_Succeeds(string name, decimal price) { var product = _productService.CreateProduct(name, price); await Assert.That(product).IsNotNull(); await Assert.That(product.Name).IsEqualTo(name); await Assert.That(product.Price).IsEqualTo(price); } [Test] [Property("Category", "Unit")] public async Task GetProduct_WhenNotFound_ReturnsNull() { var product = _productService.GetProduct(999); await Assert.That(product).IsNull(); } [Test] [MethodDataSource(nameof(InvalidProductData))] public async Task CreateProduct_WithInvalidData_ThrowsException(string name, decimal price) { await Assert.ThrowsAsync( () => _productService.CreateProduct(name, price)); } private static IEnumerable<(string name, decimal price)> InvalidProductData() { yield return ("", 10.00m); yield return ("Product", -5.00m); yield return (null!, 10.00m); } [After(Test)] public async Task Cleanup() { // Runs after each test _productService?.Dispose(); } [After(Class)] public async Task ClassCleanup() { // Runs once after all tests in the class _database?.Dispose(); } } ``` -------------------------------- ### Comparing Different Types (Bad Practice) Source: https://tunit.dev/docs/assertions/getting-started Shows examples of attempting to compare values of different, incompatible types in assertions. TUnit prevents this at compile time, but the example illustrates the potential for confusion and the need for explicit conversion. ```csharp int number = 42; // This won't compile - can't compare int to string // await Assert.That(number).IsEqualTo("42"); // Or this pattern that converts implicitly string value = GetValue(); await Assert.That(value).IsEqualTo(42); // Won't compile ``` -------------------------------- ### Advanced TUnit Collection Assertions Source: https://tunit.dev/docs/assertions/getting-started Provides examples of more advanced TUnit assertions for collections, including checking count, emptiness, membership, predicates, ordering, and equivalence. ```csharp var numbers = new[] { 1, 2, 3, 4, 5 }; // Count and emptiness await Assert.That(numbers).Count().IsEqualTo(5); await Assert.That(numbers).IsNotEmpty(); // Membership await Assert.That(numbers).Contains(3); await Assert.That(numbers).DoesNotContain(10); // Predicates await Assert.That(numbers).All(n => n > 0); await Assert.That(numbers).Any(n => n == 3); // Ordering await Assert.That(numbers).IsInOrder(); // Equivalence (same items, any order) await Assert.That(numbers).IsEquivalentTo(new[] { 5, 4, 3, 2, 1 }); ``` -------------------------------- ### Testing Numeric Ranges Source: https://tunit.dev/docs/assertions/getting-started Provides examples of asserting that a numeric value falls within a specified range or meets a minimum/maximum threshold. These assertions simplify range validation in tests. ```csharp await Assert.That(score).IsBetween(0, 100); await Assert.That(temperature).IsGreaterThanOrEqualTo(32); ``` -------------------------------- ### Forgetting to Await Assertions (Bad Practice) Source: https://tunit.dev/docs/assertions/getting-started Illustrates the common mistake of not awaiting asynchronous assertions. This leads to the assertion not executing, potentially causing tests to pass incorrectly. The example shows the incorrect syntax and explains the problem. ```csharp [Test] public void TestValue() { // Compiler warning: assertion not awaited Assert.That(result).IsEqualTo(42); } ``` -------------------------------- ### Debugging Configuration Loading in C# Source: https://tunit.dev/docs/troubleshooting Provides debugging steps and code examples for troubleshooting null configuration issues. It includes logging the current directory, listing JSON files, and verifying the `appsettings.json` file's presence and path. ```csharp [Before(HookType.Class)] public static void SetupConfiguration() { var currentDir = Directory.GetCurrentDirectory(); TestContext.Current?.WriteLine($"Current directory: {currentDir}"); var files = Directory.GetFiles(currentDir, "*.json"); TestContext.Current?.WriteLine($"JSON files: {string.Join(", ", files)}"); _configuration = new ConfigurationBuilder() .SetBasePath(currentDir) .AddJsonFile("appsettings.json", optional: false) .Build(); } ``` -------------------------------- ### TUnit Test Class Setup and Execution Source: https://tunit.dev/docs/test-lifecycle/setup Demonstrates the use of [Before(Class)], [Before(Test)], and [Test] attributes in TUnit.Core for setting up class-level and test-level fixtures, and executing tests. It includes setup for a static HTTP client and an instance variable. ```csharp using TUnit.Core; using System.Net; using System.Net.Http; namespace MyTestProject; public class MyTestClass { private int _value; private static HttpResponseMessage? _pingResponse; [Before(Class)] public static async Task Ping() { _pingResponse = await new HttpClient().GetAsync("https://localhost/ping"); } [Before(Test)] public async Task Setup() { await Task.CompletedTask; _value = 99; } [Test] public async Task MyTest() { await Assert.That(_value).IsEqualTo(99); await Assert.That(_pingResponse?.StatusCode) .IsNotNull() .And.IsEqualTo(HttpStatusCode.OK); } } ``` -------------------------------- ### Type Safety in Assertions Source: https://tunit.dev/docs/assertions/getting-started Highlights TUnit's compile-time type safety. It shows examples of correct type comparisons and explains why attempting to compare incompatible types (like int and string) will result in a compile-time error, preventing runtime issues. ```csharp int number = 42; string text = "42"; // ✅ This works - both are ints await Assert.That(number).IsEqualTo(42); // ❌ This won't compile - can't compare int to string // await Assert.That(number).IsEqualTo("42"); ``` -------------------------------- ### xUnit Assertions Example Source: https://tunit.dev/docs/comparison/framework-differences Demonstrates basic xUnit assertions for equality. This highlights a potential ambiguity in argument order without clear intellisense or documentation. ```csharp var one = 2; Assert.Equal(1, one) Assert.Equal(one, 1) ``` -------------------------------- ### Correct Configuration Timing with WebApplicationFactory Source: https://tunit.dev/docs/examples/aspnet This C# code demonstrates the correct way to configure settings for `Program.cs` startup using `WebApplicationFactory`. It highlights the difference between `ConfigureAppConfiguration` (runs after `Program.cs`) and `ConfigureStartupConfiguration` (runs before `Program.cs`). ```csharp public class WebApplicationFactory : TestWebApplicationFactory { /// /// Use ConfigureStartupConfiguration for configuration your Program.cs needs during startup. /// This runs BEFORE Program.cs. /// protected override void ConfigureStartupConfiguration(IConfigurationBuilder configurationBuilder) { configurationBuilder.AddInMemoryCollection(new Dictionary { { "SomeKey", "SomeValue" }, // Available when Program.cs runs! { "Database:ConnectionString", "..." } }); } /// /// ConfigureWebHost can still be used for other customizations, /// but NOT for configuration that Program.cs needs during startup. /// protected override void ConfigureWebHost(IWebHostBuilder builder) { // Safe to use ConfigureAppConfiguration for config that's only // needed AFTER the app has started (e.g., in controllers, services) } } ``` -------------------------------- ### Basic TUnit Assertion Syntax Source: https://tunit.dev/docs/assertions/getting-started Demonstrates the fundamental structure of a TUnit assertion, starting with `Assert.That()`, chaining assertion methods, and the required `await` keyword. This pattern is central to all TUnit assertions. ```csharp await Assert.That(actualValue).IsEqualTo(expectedValue); ``` -------------------------------- ### Manually Create and Configure TUnit Project Source: https://tunit.dev/docs/getting-started/installation These commands demonstrate the manual process of setting up a TUnit project. It involves creating a new console application, adding the TUnit package, and then removing the default Program.cs file. ```bash dotnet new console --name YourTestProjectNameHere cd YourTestProjectNameHere dotnet add package TUnit --prerelease ``` -------------------------------- ### Run Local Benchmarks with .NET CLI Source: https://tunit.dev/docs/benchmarks/methodology This snippet demonstrates how to navigate to the benchmark project, build all framework versions, and execute specific benchmarks using the .NET CLI. Ensure you have the .NET SDK installed. ```bash # 1. Navigate to benchmark project cd tools/speed-comparison # 2. Build all frameworks dotnet build -c Release # 3. Run specific benchmark cd Tests.Benchmark dotnet run -c Release -- --filter "*RuntimeBenchmarks*" ``` -------------------------------- ### GitHub Actions Workflow Example Source: https://tunit.dev/docs/execution/ci-cd-reporting A complete GitHub Actions workflow demonstrating how to set up .NET, run tests, and utilize TUnit's GitHub reporter, including explicit configuration for the full reporter style. ```yaml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: '9.0.x' - name: Run Tests with Collapsible Reporter run: dotnet test --logger "console;verbosity=normal" # GitHub reporter auto-detects and uses collapsible style by default - name: Run Tests with Full Reporter run: dotnet test -- --github-reporter-style full # Explicitly use full style for all details ``` -------------------------------- ### Chaining Assertions with .And Source: https://tunit.dev/docs/assertions/getting-started Illustrates how to chain multiple assertions on the same value using the `.And` operator for sequential checks. ```csharp await Assert.That(username) .IsNotNull() .And.IsNotEmpty() .And.Length().IsGreaterThan(3) .And.Length().IsLessThan(20); ``` -------------------------------- ### TUnit Type Checking Assertions Source: https://tunit.dev/docs/assertions/getting-started Demonstrates TUnit assertions for verifying the type of an object and checking type assignability. ```csharp await Assert.That(obj).IsTypeOf(); await Assert.That(typeof(Dog)).IsAssignableTo(); ``` -------------------------------- ### Chaining Assertions with .Or Source: https://tunit.dev/docs/assertions/getting-started Shows how to use the `.Or` operator to specify alternative conditions where any one of them being true satisfies the assertion. ```csharp await Assert.That(statusCode) .IsEqualTo(200) .Or.IsEqualTo(201) .Or.IsEqualTo(204); ``` -------------------------------- ### Example: Fixture Loading Test Cases During Discovery (C#) Source: https://tunit.dev/docs/advanced/extension-points Demonstrates a TestCaseFixture that implements IAsyncDiscoveryInitializer and IAsyncDisposable to load test cases dynamically during the discovery phase. This fixture is then used with InstanceMethodDataSource to generate tests based on the loaded data. ```csharp // Fixture that loads test cases during discovery public class TestCaseFixture : IAsyncDiscoveryInitializer, IAsyncDisposable { private List _testCases = []; public async Task InitializeAsync() { // This runs during DISCOVERY, not just execution _testCases = await LoadTestCasesFromDatabaseAsync(); } public IEnumerable GetTestCases() => _testCases; public async ValueTask DisposeAsync() { _testCases.Clear(); } } public class MyTests { [ClassDataSource(Shared = SharedType.PerClass)] public required TestCaseFixture Fixture { get; init; } public IEnumerable TestCases => Fixture.GetTestCases(); [Test] [InstanceMethodDataSource(nameof(TestCases))] public async Task MyTest(string testCase) { // Tests are generated during discovery with initialized data await Assert.That(testCase).IsNotNullOrEmpty(); } } ``` -------------------------------- ### Running .NET Tests with Coverage Source: https://tunit.dev/docs/troubleshooting Command-line examples for running .NET tests with code coverage enabled using the `dotnet run` command. Includes options for specifying configuration, output location, and format. ```bash # It's generally better to run coverage in Release configuration dotnet run --configuration Release --coverage ``` ```bash # Basic usage dotnet run --configuration Release --coverage # With output location dotnet run --configuration Release --coverage --coverage-output ./coverage/ # Specify format (cobertura, xml, etc.) dotnet run --configuration Release --coverage --coverage-output-format cobertura ``` -------------------------------- ### TUnit Exception Assertions Source: https://tunit.dev/docs/assertions/getting-started Shows how to assert that a specific exception is thrown by a code block, including checking the exception type and message. ```csharp await Assert.That(() => DivideByZero()) .Throws() .WithMessage("Attempted to divide by zero."); ``` -------------------------------- ### TUnit Boolean and Null Assertions Source: https://tunit.dev/docs/assertions/getting-started Illustrates TUnit assertions for verifying boolean states (true/false) and checking for null or default values. ```csharp await Assert.That(isValid).IsTrue(); await Assert.That(result).IsNotNull(); await Assert.That(optional).IsDefault(); ``` -------------------------------- ### Returning Values from TUnit Assertions Source: https://tunit.dev/docs/assertions/getting-started Demonstrates how certain TUnit assertions can return the value being tested, allowing for further assertions or operations on that returned value. ```csharp // HasSingleItem returns the single item var user = await Assert.That(users).HasSingleItem(); await Assert.That(user.Name).IsEqualTo("Alice"); // Contains with predicate returns the found item var admin = await Assert.That(users).Contains(u => u.Role == "Admin"); await Assert.That(admin.Permissions).IsNotEmpty(); ``` -------------------------------- ### Setup and Cleanup for File System Tests in C# Source: https://tunit.dev/docs/guides/cookbook This C# code demonstrates how to set up a temporary directory before running file system tests and clean it up afterwards using TUnit's `[Before(Test)]` and `[After(Test)]` attributes. It ensures a clean test environment by creating a unique directory for each test run and deleting it upon completion. ```csharp using TUnit.Core; using System; using System.IO; public class FileServiceTests { private string? _testDirectory; [Before(Test)] public async Task Setup() { _testDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(_testDirectory); } [After(Test)] public async Task Cleanup() { if (_testDirectory != null && Directory.Exists(_testDirectory)) { Directory.Delete(_testDirectory, recursive: true); } } // ... other tests } ``` -------------------------------- ### Grouping Assertions with Assert.Multiple() Source: https://tunit.dev/docs/assertions/getting-started Demonstrates how to group multiple assertions within an `Assert.Multiple()` block to ensure all assertions are run and all failures are reported together. ```csharp using (Assert.Multiple()) { await Assert.That(user.FirstName).IsEqualTo("John"); await Assert.That(user.LastName).IsEqualTo("Doe"); await Assert.That(user.Age).IsGreaterThan(18); await Assert.That(user.Email).IsNotNull(); } ``` -------------------------------- ### Asserting on Object Properties with .Member() Source: https://tunit.dev/docs/assertions/getting-started Explains how to use the `.Member()` assertion to check properties of an object, including nested properties, and chain assertions on those members. ```csharp await Assert.That(person) .Member(p => p.Name, name => name.IsEqualTo("Alice")) .And.Member(p => p.Age, age => age.IsGreaterThan(18)); await Assert.That(order) .Member(o => o.Customer.Address.City, city => city.IsEqualTo("Seattle"); ``` -------------------------------- ### Complete TUnit Test Example without Usings Source: https://tunit.dev/docs/comparison/framework-differences Shows a TUnit test case without explicit using statements, leveraging TUnit's global usings feature. This simplifies test files by automatically including necessary namespaces. ```C# namespace MyTests; public class ValidatorTests { [Test] public async Task IsPositive_WithNegativeNumber_ReturnsFalse() { var result = Validator.IsPositive(-1); await Assert.That(result).IsFalse(); } } ``` -------------------------------- ### Testing Floating-Point Comparisons with Tolerance Source: https://tunit.dev/docs/assertions/getting-started Illustrates how to compare floating-point numbers with a tolerance for precision issues. The .Within() method allows specifying an acceptable margin of error. ```csharp await Assert.That(3.14159).IsEqualTo(Math.PI).Within(0.001); ``` -------------------------------- ### Test File System Operations with IFileSystem Abstraction in C# Source: https://tunit.dev/docs/troubleshooting This C# example demonstrates testing file system interactions by abstracting file operations behind an IFileSystem interface. This approach allows for easy mocking of file system behavior during unit tests, promoting better testability and maintainability. ```csharp // Production code uses IFileSystem interface public class DocumentService { private readonly IFileSystem _fileSystem; public DocumentService(IFileSystem fileSystem) { _fileSystem = fileSystem; } public async Task SaveDocumentAsync(string path, string content) { await _fileSystem.File.WriteAllTextAsync(path, content); } } // Test with mock file system public class DocumentServiceTests { [Test] public async Task SaveDocument_WritesToFile() { // Arrange var mockFileSystem = new MockFileSystem(); var service = new DocumentService(mockFileSystem); // Act await service.SaveDocumentAsync("/docs/test.txt", "content"); // Assert await Assert.That(mockFileSystem.File.Exists("/docs/test.txt")).IsTrue(); var content = await mockFileSystem.File.ReadAllTextAsync("/docs/test.txt"); await Assert.That(content).IsEqualTo("content"); } } ``` -------------------------------- ### TUnit Collection Assertions Source: https://tunit.dev/docs/assertions/getting-started Demonstrates TUnit assertions for working with collections, including checking for item presence, element count, emptiness, and verifying conditions on all elements. ```csharp await Assert.That(numbers).Contains(42); await Assert.That(items).Count().IsEqualTo(5); await Assert.That(list).IsNotEmpty(); await Assert.That(values).All(x => x > 0); ``` -------------------------------- ### TUnit Basic Assertions with Assert.That Source: https://tunit.dev/docs/migration/xunit Provides examples of common assertions in TUnit using the fluent Assert.That syntax, covering equality, truthiness, null checks, and object identity. ```csharp [Test] public async Task Assertions_Examples() { await Assert.That(2 + 3).IsEqualTo(5); await Assert.That(2 + 2).IsNotEqualTo(5); await Assert.That(5 > 3).IsTrue(); await Assert.That(5 < 3).IsFalse(); await Assert.That((object?)null).IsNull(); await Assert.That("value").IsNotNull(); await Assert.That(obj1).IsSameReference(obj2); await Assert.That(obj1).IsNotSameReference(obj3); } ``` -------------------------------- ### Complete TUnit Test Example Source: https://tunit.dev/docs/getting-started/writing-your-first-test This C# code snippet demonstrates a complete TUnit test class for a calculator. It includes necessary using statements, a test method with Arrange, Act, and Assert phases, and utilizes TUnit's assertion library. This example requires the TUnit.Assertions, TUnit.Assertions.Extensions, and TUnit.Core packages. ```csharp using TUnit.Assertions; using TUnit.Assertions.Extensions; using TUnit.Core; namespace MyTestProject; public class CalculatorTests { [Test] public async Task Add_WithTwoNumbers_ReturnsSum() { // Arrange var calculator = new Calculator(); // Act var result = calculator.Add(2, 3); // Assert await Assert.That(result).IsEqualTo(5); } } ``` -------------------------------- ### Single Logical Assertion Per Test in TUnit (C#) Source: https://tunit.dev/docs/guides/best-practices Demonstrates the principle of having one logical assertion per TUnit test in C#. It shows a good example where multiple assertions verify a single behavior (user creation) and a bad example that tests unrelated behaviors within a single test method. ```csharp // ✅ Good: Multiple assertions testing one behavior (user creation) [Test] public async Task CreateUser_SetsAllProperties() { var user = await userService.CreateUser("john@example.com", "John Doe"); await Assert.That(user.Email).IsEqualTo("john@example.com"); await Assert.That(user.Name).IsEqualTo("John Doe"); await Assert.That(user.CreatedAt).IsNotEqualTo(default(DateTime)); } // ❌ Bad: Testing multiple unrelated behaviors [Test] public async Task UserService_Works() { var user = await userService.CreateUser("john@example.com", "John"); await Assert.That(user.Email).IsEqualTo("john@example.com"); await userService.DeleteUser(user.Id); var deleted = await userService.GetUser(user.Id); await Assert.That(deleted).IsNull(); } ``` -------------------------------- ### TUnit String Assertions Source: https://tunit.dev/docs/assertions/getting-started Showcases TUnit assertions for string manipulation and validation, including checking for containment, prefixes, regular expression matching, and ensuring strings are not empty. ```csharp await Assert.That(message).Contains("Hello"); await Assert.That(filename).StartsWith("test_"); await Assert.That(email).Matches(@"^[" + "\w\.-]+@[" + "\w\.-]+\."+ "\w+$"); await Assert.That(input).IsNotEmpty(); ``` -------------------------------- ### Mocking Dependencies with Moq in C# for TUnit Tests Source: https://tunit.dev/docs/guides/cookbook Illustrates how to use the Moq library to create mock objects for dependencies within TUnit tests. This example shows how to set up mock behavior, verify method calls, and handle exceptions for a payment service. It's essential for isolating the unit under test. ```csharp using Moq; using TUnit.Core; public class OrderServiceMoqTests { [Test] public async Task ProcessOrder_CallsPaymentService() { // Arrange var mockPaymentService = new Mock(); mockPaymentService .Setup(p => p.ProcessPaymentAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(new PaymentResult { Success = true, TransactionId = "TX123" }); var orderService = new OrderService(mockPaymentService.Object); var order = new Order { Total = 100.00m, PaymentMethod = "Credit Card" }; // Act var result = await orderService.ProcessAsync(order); // Assert await Assert.That(result.IsSuccess).IsTrue(); mockPaymentService.Verify( p => p.ProcessPaymentAsync(100.00m, "Credit Card"), Times.Once); } [Test] public async Task ProcessOrder_HandlesPaymentFailure() { // Arrange var mockPaymentService = new Mock(); mockPaymentService .Setup(p => p.ProcessPaymentAsync(It.IsAny(), It.IsAny())) .ThrowsAsync(new PaymentException("Insufficient funds")); var orderService = new OrderService(mockPaymentService.Object); var order = new Order { Total = 1000.00m, PaymentMethod = "Credit Card" }; // Act & Assert await Assert.That(async () => await orderService.ProcessAsync(order)) .ThrowsExactly() .WithMessage("Insufficient funds"); } } ``` -------------------------------- ### Self-Contained and Independent Test for Parallel Execution Source: https://tunit.dev/docs/guides/best-practices Provides an example of a test method designed to run independently and in parallel with other tests. It uses a unique identifier to ensure no data conflicts. ```csharp // ✅ Good: Test is self-contained and independent [Test] public async Task Can_create_order() { var orderId = Guid.NewGuid(); // Unique ID var order = new Order { Id = orderId, Total = 100 }; await orderService.CreateAsync(order); var result = await orderService.GetAsync(orderId); await Assert.That(result).IsNotNull(); } ``` -------------------------------- ### Basic Playwright Test with TUnit (C#) Source: https://tunit.dev/docs/examples/playwright Demonstrates a simple Playwright test using TUnit. The `PageTest` base class simplifies setup and teardown. This example navigates to a specified URL. ```csharp using NUnit.Framework; using TUnit.Playwright; public class Tests : PageTest { [Test] public async Task Test() { await Page.GotoAsync("https://www.github.com/thomhurst/TUnit"); } } ``` -------------------------------- ### CI/CD Environment Variable Configuration Examples Source: https://tunit.dev/docs/reference/environment-variables Illustrates how to set TUnit environment variables within popular CI/CD platforms including GitHub Actions, Azure DevOps, GitLab CI, and Dockerfiles. ```yaml - name: Run Tests env: TUNIT_DISABLE_LOGO: true TUNIT_GITHUB_REPORTER_STYLE: collapsible run: dotnet test ``` ```yaml - task: DotNetCoreCLI@2 env: TUNIT_DISABLE_LOGO: true inputs: command: 'test' ``` ```yaml test: variables: TUNIT_DISABLE_LOGO: "true" script: - dotnet test ``` ```dockerfile ENV TUNIT_DISABLE_LOGO=true ENV TUNIT_MAX_PARALLEL_TESTS=4 ``` -------------------------------- ### Discover TestContext Members in C# Source: https://tunit.dev/docs/migration/testcontext-interface-organization Provides examples of how IntelliSense groups related functionality within the TestContext, making it easier to discover and use execution, metadata, and output-related members. ```csharp TestContext.Current.Execution. // Shows only execution-related members TestContext.Current.Metadata. // Shows only metadata-related members TestContext.Current.Output. // Shows only output-related members ``` -------------------------------- ### Async Setup Operations in C# Source: https://tunit.dev/docs/examples/aspnet Implement `SetupAsync` for asynchronous operations required before the test factory is created, such as setting up database tables. The `TableName` is set here and used later in `ConfigureTestConfiguration`. ```csharp public class TodoTests : TestsBase { protected string TableName { get; private set; } = null!; protected override async Task SetupAsync() { TableName = GetIsolatedName("todos"); await CreateTableAsync(TableName); } protected override void ConfigureTestConfiguration(IConfigurationBuilder config) { // TableName is already set from SetupAsync config.AddInMemoryCollection(new Dictionary { { "Database:TableName", TableName } }); } } ``` -------------------------------- ### Sequential Assertions (Bad Practice) Source: https://tunit.dev/docs/assertions/getting-started Illustrates the drawback of using multiple, separate assertions sequentially. If an earlier assertion fails, subsequent assertions in the same test might not be executed, hiding other potential issues. ```csharp await Assert.That(user.FirstName).IsEqualTo("John"); await Assert.That(user.LastName).IsEqualTo("Doe"); await Assert.That(user.Age).IsGreaterThan(18); // If first assertion fails, you won't see the other failures ``` -------------------------------- ### Async Initialization with IAsyncInitializer in C# Source: https://tunit.dev/docs/troubleshooting Demonstrates how to perform asynchronous initialization for tests using the IAsyncInitializer interface in C#. This ensures resources like database connections are ready before tests begin execution, preventing 'Cannot await in constructor' errors. ```csharp public class DatabaseTests : IAsyncInitializer { private DatabaseConnection _connection; // Async initialization public async Task InitializeAsync() { _connection = await DatabaseConnection.CreateAsync(); } [Test] public async Task TestDatabase() { // _connection is guaranteed to be initialized } } ``` -------------------------------- ### Collection Ordering (Bad Practice) Source: https://tunit.dev/docs/assertions/getting-started Highlights the problem with assuming a specific order when comparing collections. Using `IsEqualTo` on collections can lead to unexpected test failures if the order differs, even if the elements are the same. ```csharp var items = GetItemsFromDatabase(); // Order not guaranteed await Assert.That(items).IsEqualTo(new[] { 1, 2, 3 }); ``` -------------------------------- ### Assembly Level Hooks in TUnit Source: https://tunit.dev/docs/comparison/framework-differences Shows how to implement assembly-level setup and tear-down hooks in TUnit using `[Before(Assembly)]` and `[After(Assembly)]` attributes on static methods. This allows for actions like spinning up an in-memory server for the entire test suite. ```C# using TUnit.Core; public static class AssemblyHooks { [Before(Assembly)] public static void AssemblyInitialize() { // Code to run before all tests in the assembly // e.g., Start an in-memory server } [After(Assembly)] public static void AssemblyCleanup() { // Code to run after all tests in the assembly // e.g., Stop the in-memory server } } ``` -------------------------------- ### Basic TUnit Test Structure Source: https://tunit.dev/docs/getting-started/writing-your-first-test This C# code illustrates the fundamental structure for a TUnit test. It shows how to define a namespace and a class, and importantly, how to mark a method as a test using the `[Test]` attribute. This is the minimal setup required for a runnable TUnit test. ```csharp namespace MyTestProject; public class MyTestClass { } ``` ```csharp using TUnit.Core; namespace MyTestProject; public class MyTestClass { [Test] public async Task MyTest() { } } ``` -------------------------------- ### Testing Async Operations Source: https://tunit.dev/docs/assertions/getting-started Shows how to test asynchronous operations, including asserting that they throw specific exceptions and complete within a defined time limit. This is crucial for verifying the behavior of async code. ```csharp await Assert.That(async () => await FetchDataAsync()) .Throws(); await Assert.That(longRunningTask).CompletesWithin(TimeSpan.FromSeconds(5)); ```