From ebe3dd4e50ba34932bafd9fb85aabfa0513eab45 Mon Sep 17 00:00:00 2001 From: Andrei Ignat Date: Mon, 31 Aug 2026 23:01:11 +0300 Subject: [PATCH 1/3] example --- README.md | 30 +- later.md | 2 +- v2/.tours/SatorImaging.TDoubles.tour | 48 + v2/Generator/all.csv | 1 + v2/RSCGExamplesData/GeneratorDataRec.json | 6 + .../SatorImaging.TDoubles/description.json | 22 + .../SatorImaging.TDoubles/nuget.txt | 1 + .../SatorImaging.TDoubles/readme.txt | 839 +++++++++++ .../src/Mock/IMyClock.cs | 7 + .../src/Mock/MockData.csproj | 9 + .../SatorImaging.TDoubles/src/MockRock.slnx | 4 + .../src/TestClock/TestClock.cs | 25 + .../src/TestClock/TestClock.csproj | 31 + .../src/TestClock/Usings.cs | 2 + .../SatorImaging.TDoubles/video.json | 39 + .../docs/Authors/Sator_Imaging.md | 7 + .../docs/Categories/Tests.md | 8 +- .../docs/Categories/_PrimitiveTests.mdx | 6 +- .../RSCG-Examples/SatorImaging.TDoubles.md | 1265 +++++++++++++++++ .../docs/RSCG-Examples/index.md | 13 +- v2/rscg_examples_site/docs/about.md | 2 +- v2/rscg_examples_site/docs/indexRSCG.md | 5 +- .../src/components/HomepageFeatures/index.js | 2 +- .../static/exports/RSCG.json | 8 + .../static/exports/RSCG.xlsx | Bin 14901 -> 14945 bytes 25 files changed, 2365 insertions(+), 17 deletions(-) create mode 100644 v2/.tours/SatorImaging.TDoubles.tour create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/description.json create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/nuget.txt create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/readme.txt create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/Mock/IMyClock.cs create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/Mock/MockData.csproj create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/MockRock.slnx create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.cs create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.csproj create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/Usings.cs create mode 100644 v2/rscg_examples/SatorImaging.TDoubles/video.json create mode 100644 v2/rscg_examples_site/docs/Authors/Sator_Imaging.md create mode 100644 v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md diff --git a/README.md b/README.md index 24036abed..19e54beb8 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# RSCG - 285 Examples of Roslyn Source Code Generators / 16 created by Microsoft / +# RSCG - 286 Examples of Roslyn Source Code Generators / 16 created by Microsoft / -The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 285 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. +The RSCG_Examples repository is a comprehensive documentation system that automatically processes and showcases 286 Roslyn Source Code Generator (RSCG) examples. The system transforms individual RSCG projects into structured documentation with code examples and cross-referenced content with a searchable website and code example exports. This system serves as both a learning resource for .NET developers interested in source generators and an automated pipeline for maintaining up-to-date documentation about the RSCG ecosystem -## Latest Update : 2026-08-26 => 26 August 2026 +## Latest Update : 2026-08-27 => 27 August 2026 If you want to see examples with code, please click ***[List V2](https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG)*** @@ -24,8 +24,30 @@ If you want to be notified each time I add a new RSCG example , please click htt ## Content -Those are the 285 Roslyn Source Code Generators that I have tested you can see and download source code example. +Those are the 286 Roslyn Source Code Generators that I have tested you can see and download source code example. ( including 16 from Microsoft ) +### 286. [SatorImaging.TDoubles](https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles) , in the [Tests](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#tests) category + +Generated on : 2026-08-27 => 27 August 2026 + +
+ Expand + + + +Author: Sator Imaging + +Incremental source generator for creating mock wrapper classes. + +Nuget: [https://www.nuget.org/packages/SatorImaging.TDoubles/](https://www.nuget.org/packages/SatorImaging.TDoubles/) + + +Link: [https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles](https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles) + +Source: [https://github.com/sator-imaging/TDoubles](https://github.com/sator-imaging/TDoubles) + +
+ ### 285. [PropertyResolvers](https://ignatandrei.github.io/RSCG_Examples/v2/docs/PropertyResolvers) , in the [EnhancementProject](https://ignatandrei.github.io/RSCG_Examples/v2/docs/rscg-examples#enhancementproject) category Generated on : 2026-08-26 => 26 August 2026 diff --git a/later.md b/later.md index f93b018c1..db0f7aed3 100644 --- a/later.md +++ b/later.md @@ -1,6 +1,6 @@ # Just later -## Latest Update : 2026-08-26 => 26 August 2026 +## Latest Update : 2026-08-27 => 27 August 2026 diff --git a/v2/.tours/SatorImaging.TDoubles.tour b/v2/.tours/SatorImaging.TDoubles.tour new file mode 100644 index 000000000..bd4c9366c --- /dev/null +++ b/v2/.tours/SatorImaging.TDoubles.tour @@ -0,0 +1,48 @@ + +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "SatorImaging.TDoubles", + "steps": + [ + { + "file": "rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.csproj", + "description": "First, we add Nuget [SatorImaging.TDoubles](https://www.nuget.org/packages/SatorImaging.TDoubles/) in csproj ", + "pattern": "SatorImaging.TDoubles" + } + + ,{ + "file": "rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.cs", + "description": "File TestClock.cs ", + "pattern": "this is the code" + } + + ,{ + "file": "rscg_examples/SatorImaging.TDoubles/src/Mock/IMyClock.cs", + "description": "File IMyClock.cs ", + "pattern": "this is the code" + } + + + ,{ + "file": "rscg_examples/SatorImaging.TDoubles/src/TestClock/obj/GX/SatorImaging.TDoubles/TDoubles.TDoublesSourceGenerator/_ TDoubles _ TDoublesException.g.cs", + "description": "Generated File 3 from 3 : _ TDoubles _ TDoublesException.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/SatorImaging.TDoubles/src/TestClock/obj/GX/SatorImaging.TDoubles/TDoubles.TDoublesSourceGenerator/_ TDoubles _ MockAttribute.g.cs", + "description": "Generated File 2 from 3 : _ TDoubles _ MockAttribute.g.cs ", + "line": 1 + } + + ,{ + "file": "rscg_examples/SatorImaging.TDoubles/src/TestClock/obj/GX/SatorImaging.TDoubles/TDoubles.TDoublesSourceGenerator/TestClock.QuickStartRepoStub.g.cs", + "description": "Generated File 1 from 3 : TestClock.QuickStartRepoStub.g.cs ", + "line": 1 + } + + ], + + "ref": "main" + +} \ No newline at end of file diff --git a/v2/Generator/all.csv b/v2/Generator/all.csv index 6f6c4946f..8ed5de681 100644 --- a/v2/Generator/all.csv +++ b/v2/Generator/all.csv @@ -284,3 +284,4 @@ Nr,Key,Source,Category 283,IncrementalSourceGenerator.BuilderPattern, https://github.com/MartinBarrettNielsen11/Incremental-source-generator-builder-pattern,Builder 284,Pinecone.TypedPath, https://github.com/nickpinecone/typed-path,FilesToCode 285,PropertyResolvers, https://github.com/tombiddulph/PropertyResolvers,EnhancementProject +286,SatorImaging.TDoubles, https://github.com/sator-imaging/TDoubles,Tests diff --git a/v2/RSCGExamplesData/GeneratorDataRec.json b/v2/RSCGExamplesData/GeneratorDataRec.json index 9fda4a89d..0d35fbd96 100644 --- a/v2/RSCGExamplesData/GeneratorDataRec.json +++ b/v2/RSCGExamplesData/GeneratorDataRec.json @@ -1727,6 +1727,12 @@ "Category": 1, "dtStart": "2026-08-26T00:00:00", "show": true + }, + { + "ID": "SatorImaging.TDoubles", + "Category": 13, + "dtStart": "2026-08-27T00:00:00", + "show": true } ] \ No newline at end of file diff --git a/v2/rscg_examples/SatorImaging.TDoubles/description.json b/v2/rscg_examples/SatorImaging.TDoubles/description.json new file mode 100644 index 000000000..3657d81d4 --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/description.json @@ -0,0 +1,22 @@ +{ + "generator":{ + "name":"SatorImaging.TDoubles", + "nuget":[ + "https://www.nuget.org/packages/SatorImaging.TDoubles/" + ], + "link":"https://github.com/sator-imaging/TDoubles", + "author":"Sator Imaging", + "source":"https://github.com/sator-imaging/TDoubles" + }, + "data":{ + "goodFor":["Generating test stubs with mocking for interfaces"], + "csprojDemo":"TestClock.csproj", + "csFiles":["IMyClock.cs","TestClock.cs"], + "excludeDirectoryGenerated":[""], + "includeAdditionalFiles":[""] + }, + "links":{ + "blog":"", + "video":"" + } +} \ No newline at end of file diff --git a/v2/rscg_examples/SatorImaging.TDoubles/nuget.txt b/v2/rscg_examples/SatorImaging.TDoubles/nuget.txt new file mode 100644 index 000000000..e752659fd --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/nuget.txt @@ -0,0 +1 @@ +Incremental source generator for creating mock wrapper classes. \ No newline at end of file diff --git a/v2/rscg_examples/SatorImaging.TDoubles/readme.txt b/v2/rscg_examples/SatorImaging.TDoubles/readme.txt new file mode 100644 index 000000000..c43f0cab6 --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/readme.txt @@ -0,0 +1,839 @@ +[![nuget](https://img.shields.io/nuget/vpre/SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles) +[![test](https://github.com/sator-imaging/TDoubles/actions/workflows/test.yml/badge.svg)](https://github.com/sator-imaging/TDoubles/actions/workflows/test.yml) +  +[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sator-imaging/TDoubles) + +[๐Ÿ‡บ๐Ÿ‡ธ English](./README.md) +  โ˜   +[๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž็‰ˆ](./README.ja.md) +  โ˜   +[๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡็‰ˆ](./README.zh-CN.md) + + + + + +![Hero](https://raw.githubusercontent.com/sator-imaging/TDoubles/main/GitHub-SocialPreview.png) + +`TDoubles` is a powerful C# source generator that revolutionizes unit testing by creating mock wrapper classes at compile-time. Instead of relying on complex runtime reflection or proxy generation like traditional mocking frameworks, this generator produces clean, readable C# code during compilation that wraps your target types with customizable behavior. + + + +## โœจ Comparison with Traditional Mocking Frameworks + +| Feature | TDoubles | Traditional Frameworks (Moq, NSubstitute) | +|---------|---------------------------|-------------------------------------------| +| **Performance** | Zero runtime overhead, compile-time generation | Runtime reflection and proxy creation | +| **Type Safety** | Full compile-time checking and IntelliSense | Runtime configuration, limited IntelliSense | +| **Generic Support** | Full support including constraints | Limited generic type support | +| **Setup Complexity** | Single attribute, minimal configuration | Complex fluent APIs and setup expressions | +| **Debugging** | Generated code is readable and debuggable | Proxy objects can be difficult to debug | + + + + + +# โšก Quick Start + +Apply the `[Mock]` attribute to a partial class, and the generator handles the rest. + +```cs +using TDoubles; + +public interface IDataService +{ + string GetData(int id); + void SaveData(string data); +} + +[Mock(typeof(IDataService))] // ๐Ÿ‘ˆ +partial class DataServiceMock +{ + // Implementation will be generated automatically +} +``` + + +Here shows how to use the mock in your code. + +```cs +// Create the mock +var mockService = new DataServiceMock(); + +// Override behavior for testing +mockService.MockOverrides.GetData = (id) => $"MockData_{id}"; +// ~~~~~~~~~~~~~ + +string mockData = mockService.GetData(123); // Returns "MockData_123" +``` + + +You can delegate to real implementation and override partial behaviour of the mock. + +```cs +var mock = new DataServiceMock(new ConcreteDataService()); + +// Use default behavior (delegates to real service) +var realData = mock.GetData(123); + +// Override partial behaviour for testing +mock.MockOverrides.SaveData = (data) => Console.WriteLine($"Saved: {data}"); +mock.SaveData(realData); +``` + + +Implements fake behaviors for debugging purposes in conjunction with latest update to the real implementation. + +```cs +[Mock(typeof(IFoo), nameof(IFoo.Save), nameof(IFoo.Load))] +partial class FooFake +{ + public void Save() => File.WriteAllText("...", JsonUtility.ToJson(this, true)); + public void Load() => JsonUtility.FromJsonOverwrite(File.ReadAllText("..."), this); +} + +// Delegates to latest ConcreteFoo implementation except for Save and Load +var fake = new FooFake(new ConcreteFoo()); +``` + + + + +## Generic Type Support + +`TDoubles` provides support for generic type mocking on both unbound and closed constructed generics. + +```cs +[Mock(typeof(IList))] +partial class ListIntMock {} + +// Proper type constraint for TKey is automatically generated and +// type parameter naming mismatch is also resolved +[Mock(typeof(IDictioanry<,>))] +partial class DictionaryMock {} +``` + + +## Efficient Callback Support + +There are efficient extension points to implement custom callback for each mock member call. + +> [!TIP] +> As C# specification, `partial void` method call is completely removed from built assembly when method body is not implemented in your mock class declaration. +> +> https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/partial-member + +```cs +[Mock(typeof(IList<>))] +partial class ListSpy // ๐Ÿ•ต < Investigate suspects! +{ + readonly Dictionary _callCountByName = new(); + + // Without allocating object?[] instance + partial void OnWillMockCall(string memberName) + { + if (!_callCountByName.TryGetValue(memberName, out var current)) + { + current = 0; + } + _callCountByName[memberName] = current + 1; + } + + // Another overload can take arguments passed to mock member + // * Array.Empty() is used for parameterless members + partial void OnWillMockCall(string memberName, object?[] args) + { + // How to determine method overload + if (memberName == "Add") + { + if (args[0] is T) + { + Console.WriteLine("Add(T item) is invoked."); + } + else + { + Console.WriteLine("Add(object item) is invoked."); + } + } + } +} +``` + + +## `Mock` Attribute Options + +There are options to select generated mock members. + +```cs +// Include internal types, interfaces and members to mock generation +[Mock(typeof(Foo), IncludeInternals = true)] +partial class FooMock { } + +// Exclude specified members from mock generation (no error if member is not found) +[Mock(typeof(Foo), "ToString", "Foo", "Bar", IncludeInternals = false)] +partial class FooMockWithoutToStringOverride +{ + // You can re-implement excluded 'ToString' as you desired + public override string ToString() => base.ToString() ?? ""; +} +``` + + + + + +# Introduction + +The generator works by analyzing types marked with the `[Mock]` attribute and generating corresponding mock classes that delegate to the original implementation while providing override capabilities through a simple, strongly-typed API. This approach eliminates the performance overhead of reflection-based mocking while maintaining full type safety and IntelliSense support. + + +## Key Benefits + +- **Zero Runtime Overhead**: Mock classes are generated at compile-time, eliminating reflection costs and improving test execution performance +- **Full Type Safety**: Generated mocks provide complete IntelliSense support, compile-time checking, and refactoring safety +- **Minimal Setup**: Simply add the NuGet package, apply the `[Mock]` attribute to a partial class, and the generator handles the rest +- **Universal Compatibility**: Supports all major C# type constructs including interfaces, classes, records, record structs, regular structs, and static classes +- **Advanced Generic Support**: Handles complex generic scenarios including type constraints, nested generics, and generic method overloads +- **Internal Member Access**: Optional `IncludeInternals` configuration allows mocking of internal members for comprehensive testing +- **Clean Generated Code**: Produces human-readable, debuggable mock implementations that integrate seamlessly with your codebase + + +## Use Cases + +TDoubles generator excels in scenarios where you need: + +- **High-Performance Testing**: When test execution speed is critical and reflection overhead is unacceptable +- **Legacy Code Testing**: Mocking existing classes and structs that weren't designed with interfaces +- **Static Method Testing**: Converting static methods to testable instance methods through mock wrappers +- **Record and Struct Mocking**: Testing value types and immutable records that traditional frameworks struggle with +- **Complex Generic Testing**: Mocking generic types with multiple type parameters and constraints +- **Internal API Testing**: Testing internal members without making them public + + +## How It Works + +1. **Mark Target Types**: Apply the `[Mock(typeof(TargetType))]` attribute to a partial class +2. **Compile-Time Generation**: The source generator analyzes your target type and creates a mock implementation +3. **Delegate with Overrides**: Generated mocks delegate to the original instance while providing `MockOverrides` for custom behavior +4. **Test with Confidence**: Use the generated mock in your tests with full type safety and performance + +### Delegation Logic + +Here is pseudo code of delegation. Actual code is more complicated as need to support `ref` and `out` parameter modifiers. + +```cs +public string GetData(int id) +{ + // Returns 'default' if value type or nullable reference type otherwise throws + return MockOverrides.GetData?.Invoke(id) + ?? _target?.GetData(id) + ?? throw new TDoublesException(...); +} +``` + +### Generated Mock Structure + +When you create a mock class, the generator adds several members: + +```csharp +[Mock(typeof(IUserService))] +partial class UserServiceMock +{ + // Generated by source generator: + + // Constructor that takes the target instance + public UserServiceMock(IUserService? target = default) { } + + // Access to the underlying target + public IUserService? MockTarget { get; } + + // Unified callback + partial void OnWillMockCall(string memberName); + partial void OnWillMockCall(string memberName, object?[] args); + + // Override configuration object + public sealed partial class MockOverrideContainer { } + public MockOverrideContainer MockOverrides { get; } + + // All interface/class members are implemented + public string GetUserName(int userId) { /* generated implementation */ } + public Task DeleteUser(int userId) { /* generated implementation */ } + // ... etc +} +``` + + + + + +# Installation + +## NuGet Package Installation + +### Package Manager Console + +```powershell +Install-Package SatorImaging.TDoubles +``` + +### .NET CLI + +```bash +dotnet add package SatorImaging.TDoubles +``` + +### PackageReference (Manual) + +Add the following to your project file (`.csproj`): + +```xml + +``` + + +## System Requirements + +- **.NET Framework**: .NET Standard 2.0 or higher +- **C# Language Version**: C# 7.3 or later +- **Compatible Runtimes**: + - .NET Framework 4.6.1+ + - .NET Core 2.0+ + - .NET 5.0+ + - Unity 2022.3.12f1 or later + + +## Setup and Configuration + +### Basic Setup + +1. Install the NuGet package using one of the methods above +2. Rebuild your project to enable the source generator +3. Create partial classes with the `[Mock]` attribute to generate mocks + +### Project Configuration + +No additional project configuration is required. The source generator automatically activates when the package is installed and will generate mock classes during compilation. + +### Verification + +To verify the installation was successful: + +1. Add a simple mock class to your project: + ```csharp + using TDoubles; + + public interface ITestService + { + string GetMessage(); + } + + [Mock(typeof(ITestService))] + partial class TestServiceMock + { + // Mock implementation will be generated here + } + ``` +2. Build your project +3. Check that no compilation errors occur and the mock class is generated + + +### IDE Support +- **Visual Studio**: Full IntelliSense support for generated mock classes +- **Visual Studio Code**: Works with C# extension +- **JetBrains Rider**: Full support with code completion +- **Command Line**: Works with `dotnet build` and `msbuild` + + + + + +# Basic Usage + +This section provides step-by-step examples to get you started with `TDoubles`. All examples are complete and ready to use in your projects. + + +## Prerequisites + +Before using the TDoubles generator, ensure your mock classes meet these requirements: + +1. **Partial Class**: Mock classes must be declared as `partial` +2. **Mock Attribute**: Apply `[Mock(typeof(TargetType))]` to the partial class +3. **Namespace**: Include `using TDoubles;` +4. **Visibility**: Use any visibility modifier (public, internal, etc.) - generated members will inherit the same visibility + + +## Simple Interface Mocking + +The most common scenario is mocking interfaces for dependency injection testing. + +### Example: User Service Interface + +```csharp +using TDoubles; +using System; +using System.Threading.Tasks; + +// Define your interface +public interface IUserService +{ + string GetUserName(int userId); + Task DeleteUser(int userId); + bool IsUserActive(int userId); +} + +// Create a partial mock class +[Mock(typeof(IUserService))] +partial class UserServiceMock +{ + // The source generator will create the complete implementation here +} + +// Example usage in tests +class Program +{ + static void Main() + { + // Create a concrete implementation for delegation + var realService = new ConcreteUserService(); + + // Create the mock with the real service as the underlying target + var mockService = new UserServiceMock(realService); + + Console.WriteLine("=== Default Behavior (Delegates to Real Service) ==="); + Console.WriteLine($"User Name: {mockService.GetUserName(123)}"); + Console.WriteLine($"Is Active: {mockService.IsUserActive(123)}"); + + Console.WriteLine("\n=== Custom Behavior with Overrides ==="); + + // Override specific methods for testing + mockService.MockOverrides.GetUserName = (userId) => $"MockUser_{userId}"; + mockService.MockOverrides.IsUserActive = (userId) => userId > 100; + + Console.WriteLine($"User Name (Overridden): {mockService.GetUserName(123)}"); + Console.WriteLine($"Is Active (Overridden): {mockService.IsUserActive(50)}"); + Console.WriteLine($"Is Active (Overridden): {mockService.IsUserActive(150)}"); + + // Access the underlying real service if needed + Console.WriteLine($"Real Service: {mockService.MockTarget.GetUserName(123)}"); + } +} + +// Concrete implementation for demonstration +public class ConcreteUserService : IUserService +{ + public string GetUserName(int userId) => $"RealUser_{userId}"; + public async Task DeleteUser(int userId) => await Task.FromResult(true); + public bool IsUserActive(int userId) => true; +} +``` + + +## Class Mocking with Inheritance + +Mock concrete classes to test inheritance scenarios and virtual method overrides. + +### Example: Service Class with Virtual Methods + +```csharp +using TDoubles; +using System; + +// Base service class with virtual methods +public class DatabaseService +{ + public virtual string GetConnectionString() => "Server=localhost;Database=prod;"; + public virtual void SaveData(string data) => Console.WriteLine($"Saving to database: {data}"); + public virtual int GetRecordCount() => 1000; + + // Non-virtual method (will be wrapped but not overridable) + public string GetServiceName() => "DatabaseService"; +} + +// Create mock for the class +[Mock(typeof(DatabaseService))] +partial class DatabaseServiceMock +{ + // Generated implementation will wrap all public methods +} + +// Example usage +class Program +{ + static void Main() + { + // Create real service instance + var realService = new DatabaseService(); + + // Create mock wrapper + var mockService = new DatabaseServiceMock(realService); + + Console.WriteLine("=== Default Behavior ==="); + Console.WriteLine($"Connection: {mockService.GetConnectionString()}"); + Console.WriteLine($"Service Name: {mockService.GetServiceName()}"); + Console.WriteLine($"Record Count: {mockService.GetRecordCount()}"); + mockService.SaveData("test data"); + + Console.WriteLine("\n=== Testing Scenario Overrides ==="); + + // Override for testing scenarios + mockService.MockOverrides.GetConnectionString = () => "Server=testserver;Database=test;"; + mockService.MockOverrides.GetRecordCount = () => 0; // Simulate empty database + mockService.MockOverrides.SaveData = (data) => Console.WriteLine($"TEST MODE: Would save '{data}'"); + + Console.WriteLine($"Test Connection: {mockService.GetConnectionString()}"); + Console.WriteLine($"Test Record Count: {mockService.GetRecordCount()}"); + mockService.SaveData("test data"); + + // Non-virtual methods still work but delegate to original + Console.WriteLine($"Service Name (always delegates): {mockService.GetServiceName()}"); + } +} +``` + + +## Inheritance and Interface Implementation + +Mock classes that both inherit from base classes and implement interfaces. + +### Example: Complex Service Hierarchy + +```csharp +using TDoubles; +using System; + +// Interface definition +public interface INotificationService +{ + void SendNotification(string message); + bool IsServiceAvailable(); +} + +// Base class with virtual methods +public class BaseService +{ + public virtual string GetServiceType() => "Base"; + public virtual void Initialize() => Console.WriteLine("Base initialization"); +} + +// Concrete class that inherits and implements interface +public class EmailService : BaseService, INotificationService +{ + public override string GetServiceType() => "Email"; + public override void Initialize() => Console.WriteLine("Email service initialized"); + + public void SendNotification(string message) => Console.WriteLine($"Email: {message}"); + public bool IsServiceAvailable() => true; +} + +// Mock the concrete class +[Mock(typeof(EmailService))] +partial class EmailServiceMock +{ + // Mocks both inherited methods and interface implementations +} + +// Usage example +class Program +{ + static void Main() + { + var realService = new EmailService(); + var mockService = new EmailServiceMock(realService); + + Console.WriteLine("=== Testing Inherited Methods ==="); + Console.WriteLine($"Service Type: {mockService.GetServiceType()}"); + mockService.Initialize(); + + Console.WriteLine("\n=== Testing Interface Methods ==="); + mockService.SendNotification("Hello World"); + Console.WriteLine($"Available: {mockService.IsServiceAvailable()}"); + + Console.WriteLine("\n=== Testing with Overrides ==="); + + // Override inherited method + mockService.MockOverrides.GetServiceType = () => "MockEmail"; + mockService.MockOverrides.Initialize = () => Console.WriteLine("Mock initialization"); + + // Override interface methods + mockService.MockOverrides.SendNotification = (msg) => Console.WriteLine($"MOCK EMAIL: {msg}"); + mockService.MockOverrides.IsServiceAvailable = () => false; + + Console.WriteLine($"Service Type: {mockService.GetServiceType()}"); + mockService.Initialize(); + mockService.SendNotification("Test Message"); + Console.WriteLine($"Available: {mockService.IsServiceAvailable()}"); + } +} +``` + + + + + +# Advanced Usage + +For advanced scenarios including generic types, static classes, records, structs, and internal member access, see the [Advanced Usage Guide](docs/advanced-usage.md). + + + + + +# Testing Examples + +For comprehensive testing examples with MSTest, NUnit, and performance comparisons, see the [Testing Examples Guide](docs/testing-examples.md). + + + + + + + + + + +# Technical Note + +## `record` and `record struct` + +- Always implements `IEquatable` and `MockOverrides.MockTargetRecord_Equals` + - Note that it is *NOT* `IEquatable` +- `bool Equals(object?)` cannot be overridden + + + + + +# Known Limitations and Unsupported Scenarios + +## Type Parameters of Generic Method + +When method uses method-level type parameter instead of type-level parameter, `MockOverrides` will use `object` instead of method-level type parameter. + +```cs +// Generated mock has type-level parameter T +partial class Mock +{ + // Generated mock method that has T and TMethod type parameter + public TMethod GenericMethod(T input) { ... } + + // can be added to this class but it must also be exposed as type-level parameter... + public sealed partial class MockOverrideContainer + { + // type-level parameter T is used but TMethod is shadowed to object + public Func GenericMethod { get; set; } + // ~~~~~~ Not TMethod + } +} +``` + +> [!NOTE] +> Generated mock method returns `TMethod` as mock target does. Internally, mock method will cast `object` result from override to `TMethod` when returning value. + + +## Type System Limitations + +**Unsupported Types:** +- Enums (use wrapper classes instead) +- Delegates and function pointers +- Primitive types (`int`, `string`, etc.) +- Static classes with only static constructors +- Abstract classes with pure virtual methods requiring implementation +- `object`, `ValueType`, `Enum` and other special types such as `Span` + + +## Type Constraint Limitations + +**Unsupported Constraints:** +- `where T : default` +- `where T : allows ref struct` + + +## Return Type Limitations + +**Unsupported Type:** +- `ref` return type + + +## Attribute Limitations + +Attributes on type, method, property or etc are not preserved in generated mock. + + +## Method and Property Limitations + +**Unsupported Members:** +- `ref` and `out` parameters in some complex scenarios (?) +- ~~Methods with `__arglist` (variable arguments)~~ +- ~~Explicit interface implementations with name conflicts~~ +- ~~Properties with complex getter/setter accessibility combinations~~ + +**Partial Support:** + +```csharp +public interface IService +{ + // โœ… Fully supported + string GetData(int id); + Task ProcessAsync(string data); + + // โš ๏ธ Limited support - may not override correctly + ref int GetReference(); + void ProcessData(__arglist); +} +``` + + +## Generic Method Limitations + +Some valid type constraint is not transformed correctly. We have no plan to support this edge case of type constraint. + +> Note: `override` method cannot have type constraint except for `class` and `struct`. + +```cs +// Abstract method declaration that returns (M, N?) with where M : N? constraint +public abstract (M t, N? u) TypeArgMappingNullable_Abstract() where M : N?; + +// Expected (valid) return type is (M, N) +public override (M t, N u) TypeArgMappingNullable_Abstract() { } + +// But got (M, N?) +public override (M t, N? u) TypeArgMappingNullable_Abstract() { } +``` + + + +## Inheritance and Interface Limitations + +**Multiple Interface Implementation:** +- ~~Supported, but explicit interface implementations may have naming conflicts~~ +- Diamond inheritance patterns may cause method resolution issues + +**Virtual Method Overriding:** +- ~~Only `virtual` and `abstract` methods can be overridden in class mocks~~ +- `sealed` methods cannot be overridden (will delegate to original) + + +## Platform and Framework Limitations + +**Framework Support:** +- Requires .NET Standard 2.0 or higher +- Source generators require C# 7.3 or later +- Some advanced C# 11+ features may not be fully supported + +**IDE Integration:** +- IntelliSense may be delayed for newly generated mocks +- Some IDEs may require rebuild to recognize generated code +- Debugging generated code may show optimized/synthetic code + + + + + +# Contributing + +We welcome and appreciate contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or providing feedback, your contributions help make TDoubles better for everyone. + +See [CONTRIBUTING.md](CONTRIBUTING.md) + + + + + +# Code of Conduct + +We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and professional in all interactions. + + + + + +# Support and Community + +## Getting Help + +If you encounter issues not covered in this troubleshooting guide: + +1. **Check the GitHub Issues:** Search existing issues for similar problems +2. **Create a Minimal Reproduction:** Provide the smallest possible code example that demonstrates the issue +3. **Include Build Output:** Share relevant compiler errors and warnings +4. **Specify Environment:** Include .NET version, IDE, and operating system details + +**Support Channels:** +- [GitHub Discussions](https://github.com/sator-imaging/TDoubles/discussions) - Questions and community support +- [GitHub Issues](https://github.com/sator-imaging/TDoubles/issues) - Bug reports and feature requests + + +## Reporting Security Issues + +If you discover a security vulnerability, please report it privately by emailing the maintainers rather than creating a public issue. This allows us to address the issue before it becomes widely known. + + + + + +# Project Information + +## TODO: Help Wanted + +- Missing Tests + - `static` class mocking + - `sealed` overridden methods + - `async` tests + - `event` getter and setter tests + - `readonly struct` tests + - `readonly record struct` tests + - `Tuple` and `ValueTuple` tests + - Property and indexer accessibility tests (e.g., `{ get; private set; }` or etc) +- Missing Features + - `ref` return + - Attribute preservation + - Add proper `` for mock members + - Support for `default` and `allows ref struct` type constraint + - The `default` constraint is valid on override and explicit interface implementation methods only + - Roslyn update is required while keeping Unity engine support + - Emit diagnostic error on type parameters +- Optimization + - Use `ImmutableArray` or `ImmutableList` as possible + - Eliminate inefficient `StringBuilder` use +- Refactor + - Eliminate FP programming techniques + - Transform data model to domain model to encapsulate information and behaviour + - Centralize blueprint-to-C# conversion in domain model to make it consistent, robust and maintainable + - Eliminate duplicate functions, control flows and etc scattered in codebase +- Optional + - ~~New `Mock` attribute option to generate `MockCallCounts` that records the call count of each mock member~~ + - Declare `volatile int` fields + - Increment count by `Interlocked.Increment(ref ...)` method at the beginning of generated mock class member. + + +## Author and Maintainer + +**Sator Imaging** +- GitHub: [@sator-imaging](https://github.com/sator-imaging) +- Project Repository: [sator-imaging/TDoubles](https://github.com/sator-imaging/TDoubles) + + +## Acknowledgments + +We thank all contributors who have helped improve this project through code contributions, bug reports, feature suggestions, and community support. + + +## License + +This project is licensed under the **MIT License**. + +### Third-Party Licenses + +This project uses the following third-party packages: +- **Microsoft.CodeAnalysis.CSharp** (MIT License) +- **Microsoft.CodeAnalysis.Analyzers** (MIT License) + + + + + +--- + +**© 2025 Sator Imaging. All rights reserved.** + +For support, questions, or contributions, please visit our [GitHub repository](https://github.com/sator-imaging/TDoubles). diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/IMyClock.cs b/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/IMyClock.cs new file mode 100644 index 000000000..b5f9f222d --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/IMyClock.cs @@ -0,0 +1,7 @@ +๏ปฟnamespace MockData; + +public interface IMyClock +{ + public DateTime GetNow(); + public DateTime GetUtcNow(); +} \ No newline at end of file diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/MockData.csproj b/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/MockData.csproj new file mode 100644 index 000000000..9ed914b5b --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/Mock/MockData.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/MockRock.slnx b/v2/rscg_examples/SatorImaging.TDoubles/src/MockRock.slnx new file mode 100644 index 000000000..e2f5c864b --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/MockRock.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.cs b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.cs new file mode 100644 index 000000000..b995b76b9 --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.cs @@ -0,0 +1,25 @@ + + +namespace TestClock; + +[TDoubles.Mock(typeof(IMyClock))] +public partial class QuickStartRepoStub { } + + +[TestClass] +public class TestClock +{ + [TestMethod] + public void TestMyClock() + { + var expectations = new QuickStartRepoStub(); + expectations.MockOverrides.GetNow =()=>(DateTime.Now.AddYears(-1)); + + IMyClock mock = expectations; + var data= mock.GetNow(); + Assert.AreEqual(DateTime.Now.Year -1, data.Year); + + } +} + + diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.csproj b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.csproj new file mode 100644 index 000000000..43af4a47d --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/TestClock.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + enable + enable + + false + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + true + $(BaseIntermediateOutputPath)\GX + + + diff --git a/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/Usings.cs b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/Usings.cs new file mode 100644 index 000000000..f3be1f99c --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/src/TestClock/Usings.cs @@ -0,0 +1,2 @@ +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using MockData; diff --git a/v2/rscg_examples/SatorImaging.TDoubles/video.json b/v2/rscg_examples/SatorImaging.TDoubles/video.json new file mode 100644 index 000000000..25248a826 --- /dev/null +++ b/v2/rscg_examples/SatorImaging.TDoubles/video.json @@ -0,0 +1,39 @@ +{ + "scriptName": "SatorImaging.TDoubles", + "steps": +[ + {"typeStep":"exec","arg":"clipchamp.exe launch"}, + {"typeStep":"text","arg": "Welcome to Roslyn Examples"}, + {"typeStep":"text","arg":"If you want to see more examples , see List Of RSCG"}, + {"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG"}, + {"typeStep":"text","arg": "My name is Andrei Ignat and I am deeply fond of Roslyn Source Code Generator. "}, + +{"typeStep":"text","arg": "Today I will present SatorImaging.TDoubles . Generating test stubs with mocking for interfaces ."}, +{"typeStep":"browser","arg":"https://www.nuget.org/packages/SatorImaging.TDoubles/"}, +{"typeStep":"text","arg": "The whole example is here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles"}, +{"typeStep":"text","arg": "You can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles#download-example-net--c-"}, +{"typeStep":"text","arg":"Here is the code downloaded "}, +{"typeStep":"exec","arg":"explorer.exe /select,D:\\gth\\RSCG_Examples\\v2\\Generator.sln"}, +{"typeStep":"text","arg": "So , let's start the project with Visual Studio Code "}, +{"typeStep":"stepvscode","arg": "-n D:\\gth\\RSCG_Examples\\v2"}, + +{"typeStep":"text","arg": "To use it ,you will put the Nuget SatorImaging.TDoubles into the csproj "}, + +{"typeStep":"stepvscode","arg": "-r -g D:\\gth\\RSCG_Examples\\v2\\rscg_examples\\SatorImaging.TDoubles\\src\\TestClock\\TestClock.csproj"}, + +{"typeStep":"text","arg": "And now I will show you an example of using SatorImaging.TDoubles"}, + +{"typeStep":"hide","arg": "now execute the tour in VSCode"}, +{"typeStep":"tour", "arg": "src/.tours/"}, +{"typeStep":"text","arg":" And I will execute the project"}, +{"typeStep":"showproj", "arg":"TestClock.csproj"}, +{"typeStep":"text","arg":" This concludes the project"}, +{"typeStep":"waitseconds","arg":"30"}, +{"typeStep":"text","arg": "Remember, you can download the code from here"}, +{"typeStep":"browser","arg":"https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles#download-example-net--c-", +SpeakTest=" "}, +{"typeStep":"waitseconds","arg":"30"}, +] +} diff --git a/v2/rscg_examples_site/docs/Authors/Sator_Imaging.md b/v2/rscg_examples_site/docs/Authors/Sator_Imaging.md new file mode 100644 index 000000000..515dcba79 --- /dev/null +++ b/v2/rscg_examples_site/docs/Authors/Sator_Imaging.md @@ -0,0 +1,7 @@ +# Author : Sator Imaging + +Number RSCG: 1 + + + 1 [SatorImaging.TDoubles](/docs/SatorImaging.TDoubles) [![Nuget](https://img.shields.io/nuget/dt/SatorImaging.TDoubles?label=SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles/) ![GitHub Repo stars](https://img.shields.io/github/stars/sator-imaging/TDoubles?style=social) 2026-08-27 + diff --git a/v2/rscg_examples_site/docs/Categories/Tests.md b/v2/rscg_examples_site/docs/Categories/Tests.md index 8e3d45901..b73e6b25b 100644 --- a/v2/rscg_examples_site/docs/Categories/Tests.md +++ b/v2/rscg_examples_site/docs/Categories/Tests.md @@ -1,6 +1,6 @@

Tests

-Number RSCG: 9 +Number RSCG: 10 1 [Imposter](/docs/Imposter) [![Nuget](https://img.shields.io/nuget/dt/Imposter?label=Imposter)](https://www.nuget.org/packages/Imposter/) ![GitHub Repo stars](https://img.shields.io/github/stars/themidnightgospel/Imposter?style=social) 2025-12-13 @@ -16,7 +16,9 @@ Number RSCG: 9 7 [Rocks](/docs/Rocks) [![Nuget](https://img.shields.io/nuget/dt/Rocks?label=Rocks)](https://www.nuget.org/packages/Rocks/) ![GitHub Repo stars](https://img.shields.io/github/stars/JasonBock/Rocks?style=social) 2023-04-16 - 8 [TinyBDD.MSTest](/docs/TinyBDD.MSTest) [![Nuget](https://img.shields.io/nuget/dt/TinyBDD.MSTest?label=TinyBDD.MSTest)](https://www.nuget.org/packages/TinyBDD.MSTest/) ![GitHub Repo stars](https://img.shields.io/github/stars/JerrettDavis/TinyBDD?style=social) 2026-07-03 + 8 [SatorImaging.TDoubles](/docs/SatorImaging.TDoubles) [![Nuget](https://img.shields.io/nuget/dt/SatorImaging.TDoubles?label=SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles/) ![GitHub Repo stars](https://img.shields.io/github/stars/sator-imaging/TDoubles?style=social) 2026-08-27 - 9 [TUnit](/docs/TUnit) [![Nuget](https://img.shields.io/nuget/dt/TUnit?label=TUnit)](https://www.nuget.org/packages/TUnit/) ![GitHub Repo stars](https://img.shields.io/github/stars/thomhurst/TUnit?style=social) 2025-11-08 + 9 [TinyBDD.MSTest](/docs/TinyBDD.MSTest) [![Nuget](https://img.shields.io/nuget/dt/TinyBDD.MSTest?label=TinyBDD.MSTest)](https://www.nuget.org/packages/TinyBDD.MSTest/) ![GitHub Repo stars](https://img.shields.io/github/stars/JerrettDavis/TinyBDD?style=social) 2026-07-03 + + 10 [TUnit](/docs/TUnit) [![Nuget](https://img.shields.io/nuget/dt/TUnit?label=TUnit)](https://www.nuget.org/packages/TUnit/) ![GitHub Repo stars](https://img.shields.io/github/stars/thomhurst/TUnit?style=social) 2025-11-08 \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/Categories/_PrimitiveTests.mdx b/v2/rscg_examples_site/docs/Categories/_PrimitiveTests.mdx index 40d8904ee..ca7a074f9 100644 --- a/v2/rscg_examples_site/docs/Categories/_PrimitiveTests.mdx +++ b/v2/rscg_examples_site/docs/Categories/_PrimitiveTests.mdx @@ -14,9 +14,11 @@ 7 [Rocks](/docs/Rocks) [![Nuget](https://img.shields.io/nuget/dt/Rocks?label=Rocks)](https://www.nuget.org/packages/Rocks/) ![GitHub Repo stars](https://img.shields.io/github/stars/JasonBock/Rocks?style=social) 2023-04-16 - 8 [TinyBDD.MSTest](/docs/TinyBDD.MSTest) [![Nuget](https://img.shields.io/nuget/dt/TinyBDD.MSTest?label=TinyBDD.MSTest)](https://www.nuget.org/packages/TinyBDD.MSTest/) ![GitHub Repo stars](https://img.shields.io/github/stars/JerrettDavis/TinyBDD?style=social) 2026-07-03 + 8 [SatorImaging.TDoubles](/docs/SatorImaging.TDoubles) [![Nuget](https://img.shields.io/nuget/dt/SatorImaging.TDoubles?label=SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles/) ![GitHub Repo stars](https://img.shields.io/github/stars/sator-imaging/TDoubles?style=social) 2026-08-27 - 9 [TUnit](/docs/TUnit) [![Nuget](https://img.shields.io/nuget/dt/TUnit?label=TUnit)](https://www.nuget.org/packages/TUnit/) ![GitHub Repo stars](https://img.shields.io/github/stars/thomhurst/TUnit?style=social) 2025-11-08 + 9 [TinyBDD.MSTest](/docs/TinyBDD.MSTest) [![Nuget](https://img.shields.io/nuget/dt/TinyBDD.MSTest?label=TinyBDD.MSTest)](https://www.nuget.org/packages/TinyBDD.MSTest/) ![GitHub Repo stars](https://img.shields.io/github/stars/JerrettDavis/TinyBDD?style=social) 2026-07-03 + + 10 [TUnit](/docs/TUnit) [![Nuget](https://img.shields.io/nuget/dt/TUnit?label=TUnit)](https://www.nuget.org/packages/TUnit/) ![GitHub Repo stars](https://img.shields.io/github/stars/thomhurst/TUnit?style=social) 2025-11-08 ### See category diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md b/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md new file mode 100644 index 000000000..3f7622416 --- /dev/null +++ b/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md @@ -0,0 +1,1265 @@ +--- +sidebar_position: 2860 +title: 286 - SatorImaging.TDoubles +description: Generating test stubs with mocking for interfaces +slug: /SatorImaging.TDoubles +--- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import TOCInline from '@theme/TOCInline'; +import SameCategory from '../Categories/_PrimitiveTests.mdx'; + +# SatorImaging.TDoubles by Sator Imaging + + + + +## NuGet / site data +[![Nuget](https://img.shields.io/nuget/dt/SatorImaging.TDoubles?label=SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles/) +[![GitHub last commit](https://img.shields.io/github/last-commit/sator-imaging/TDoubles?label=updated)](https://github.com/sator-imaging/TDoubles) +![GitHub Repo stars](https://img.shields.io/github/stars/sator-imaging/TDoubles?style=social) + +## Details + +### Info +:::info + +Name: **SatorImaging.TDoubles** + +Incremental source generator for creating mock wrapper classes. + +Author: Sator Imaging + +NuGet: +*https://www.nuget.org/packages/SatorImaging.TDoubles/* + + +You can find more details at https://github.com/sator-imaging/TDoubles + +Source: https://github.com/sator-imaging/TDoubles + +::: + +### Author +:::note +Sator Imaging +![Alt text](https://github.com/sator-imaging.png) +::: + +## Original Readme +:::note + +[![nuget](https://img.shields.io/nuget/vpre/SatorImaging.TDoubles)](https://www.nuget.org/packages/SatorImaging.TDoubles) +[![test](https://github.com/sator-imaging/TDoubles/actions/workflows/test.yml/badge.svg)](https://github.com/sator-imaging/TDoubles/actions/workflows/test.yml) +  +[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sator-imaging/TDoubles) + +[๐Ÿ‡บ๐Ÿ‡ธ English](./README.md) +  โ˜   +[๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž็‰ˆ](./README.ja.md) +  โ˜   +[๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡็‰ˆ](./README.zh-CN.md) + + + + + +![Hero](https://raw.githubusercontent.com/sator-imaging/TDoubles/main/GitHub-SocialPreview.png) + +`TDoubles` is a powerful C# source generator that revolutionizes unit testing by creating mock wrapper classes at compile-time. Instead of relying on complex runtime reflection or proxy generation like traditional mocking frameworks, this generator produces clean, readable C# code during compilation that wraps your target types with customizable behavior. + + + +## โœจ Comparison with Traditional Mocking Frameworks + +| Feature | TDoubles | Traditional Frameworks (Moq, NSubstitute) | +|---------|---------------------------|-------------------------------------------| +| **Performance** | Zero runtime overhead, compile-time generation | Runtime reflection and proxy creation | +| **Type Safety** | Full compile-time checking and IntelliSense | Runtime configuration, limited IntelliSense | +| **Generic Support** | Full support including constraints | Limited generic type support | +| **Setup Complexity** | Single attribute, minimal configuration | Complex fluent APIs and setup expressions | +| **Debugging** | Generated code is readable and debuggable | Proxy objects can be difficult to debug | + + + + + +# โšก Quick Start + +Apply the `[Mock]` attribute to a partial class, and the generator handles the rest. + +```cs +using TDoubles; + +public interface IDataService +{ + string GetData(int id); + void SaveData(string data); +} + +[Mock(typeof(IDataService))] // ๐Ÿ‘ˆ +partial class DataServiceMock +{ + // Implementation will be generated automatically +} +``` + + +Here shows how to use the mock in your code. + +```cs +// Create the mock +var mockService = new DataServiceMock(); + +// Override behavior for testing +mockService.MockOverrides.GetData = (id) => $"MockData_{id}"; +// ~~~~~~~~~~~~~ + +string mockData = mockService.GetData(123); // Returns "MockData_123" +``` + + +You can delegate to real implementation and override partial behaviour of the mock. + +```cs +var mock = new DataServiceMock(new ConcreteDataService()); + +// Use default behavior (delegates to real service) +var realData = mock.GetData(123); + +// Override partial behaviour for testing +mock.MockOverrides.SaveData = (data) => Console.WriteLine($"Saved: {data}"); +mock.SaveData(realData); +``` + + +Implements fake behaviors for debugging purposes in conjunction with latest update to the real implementation. + +```cs +[Mock(typeof(IFoo), nameof(IFoo.Save), nameof(IFoo.Load))] +partial class FooFake +{ + public void Save() => File.WriteAllText("...", JsonUtility.ToJson(this, true)); + public void Load() => JsonUtility.FromJsonOverwrite(File.ReadAllText("..."), this); +} + +// Delegates to latest ConcreteFoo implementation except for Save and Load +var fake = new FooFake(new ConcreteFoo()); +``` + + + + +## Generic Type Support + +`TDoubles` provides support for generic type mocking on both unbound and closed constructed generics. + +```cs +[Mock(typeof(IList))] +partial class ListIntMock {} + +// Proper type constraint for TKey is automatically generated and +// type parameter naming mismatch is also resolved +[Mock(typeof(IDictioanry<,>))] +partial class DictionaryMock {} +``` + + +## Efficient Callback Support + +There are efficient extension points to implement custom callback for each mock member call. + +> [!TIP] +> As C# specification, `partial void` method call is completely removed from built assembly when method body is not implemented in your mock class declaration. +> +> https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/partial-member + +```cs +[Mock(typeof(IList<>))] +partial class ListSpy // ๐Ÿ•ต < Investigate suspects! +{ + readonly Dictionary _callCountByName = new(); + + // Without allocating object?[] instance + partial void OnWillMockCall(string memberName) + { + if (!_callCountByName.TryGetValue(memberName, out var current)) + { + current = 0; + } + _callCountByName[memberName] = current + 1; + } + + // Another overload can take arguments passed to mock member + // * Array.Empty() is used for parameterless members + partial void OnWillMockCall(string memberName, object?[] args) + { + // How to determine method overload + if (memberName == "Add") + { + if (args[0] is T) + { + Console.WriteLine("Add(T item) is invoked."); + } + else + { + Console.WriteLine("Add(object item) is invoked."); + } + } + } +} +``` + + +## `Mock` Attribute Options + +There are options to select generated mock members. + +```cs +// Include internal types, interfaces and members to mock generation +[Mock(typeof(Foo), IncludeInternals = true)] +partial class FooMock \{ } + +// Exclude specified members from mock generation (no error if member is not found) +[Mock(typeof(Foo), "ToString", "Foo", "Bar", IncludeInternals = false)] +partial class FooMockWithoutToStringOverride +{ + // You can re-implement excluded 'ToString' as you desired + public override string ToString() => base.ToString() ?? ""; +} +``` + + + + + +# Introduction + +The generator works by analyzing types marked with the `[Mock]` attribute and generating corresponding mock classes that delegate to the original implementation while providing override capabilities through a simple, strongly-typed API. This approach eliminates the performance overhead of reflection-based mocking while maintaining full type safety and IntelliSense support. + + +## Key Benefits + +- **Zero Runtime Overhead**: Mock classes are generated at compile-time, eliminating reflection costs and improving test execution performance +- **Full Type Safety**: Generated mocks provide complete IntelliSense support, compile-time checking, and refactoring safety +- **Minimal Setup**: Simply add the NuGet package, apply the `[Mock]` attribute to a partial class, and the generator handles the rest +- **Universal Compatibility**: Supports all major C# type constructs including interfaces, classes, records, record structs, regular structs, and static classes +- **Advanced Generic Support**: Handles complex generic scenarios including type constraints, nested generics, and generic method overloads +- **Internal Member Access**: Optional `IncludeInternals` configuration allows mocking of internal members for comprehensive testing +- **Clean Generated Code**: Produces human-readable, debuggable mock implementations that integrate seamlessly with your codebase + + +## Use Cases + +TDoubles generator excels in scenarios where you need: + +- **High-Performance Testing**: When test execution speed is critical and reflection overhead is unacceptable +- **Legacy Code Testing**: Mocking existing classes and structs that weren't designed with interfaces +- **Static Method Testing**: Converting static methods to testable instance methods through mock wrappers +- **Record and Struct Mocking**: Testing value types and immutable records that traditional frameworks struggle with +- **Complex Generic Testing**: Mocking generic types with multiple type parameters and constraints +- **Internal API Testing**: Testing internal members without making them public + + +## How It Works + +1. **Mark Target Types**: Apply the `[Mock(typeof(TargetType))]` attribute to a partial class +2. **Compile-Time Generation**: The source generator analyzes your target type and creates a mock implementation +3. **Delegate with Overrides**: Generated mocks delegate to the original instance while providing `MockOverrides` for custom behavior +4. **Test with Confidence**: Use the generated mock in your tests with full type safety and performance + +### Delegation Logic + +Here is pseudo code of delegation. Actual code is more complicated as need to support `ref` and `out` parameter modifiers. + +```cs +public string GetData(int id) +{ + // Returns 'default' if value type or nullable reference type otherwise throws + return MockOverrides.GetData?.Invoke(id) + ?? _target?.GetData(id) + ?? throw new TDoublesException(...); +} +``` + +### Generated Mock Structure + +When you create a mock class, the generator adds several members: + +```csharp +[Mock(typeof(IUserService))] +partial class UserServiceMock +{ + // Generated by source generator: + + // Constructor that takes the target instance + public UserServiceMock(IUserService? target = default) \{ } + + // Access to the underlying target + public IUserService? MockTarget \{ get; } + + // Unified callback + partial void OnWillMockCall(string memberName); + partial void OnWillMockCall(string memberName, object?[] args); + + // Override configuration object + public sealed partial class MockOverrideContainer \{ } + public MockOverrideContainer MockOverrides \{ get; } + + // All interface/class members are implemented + public string GetUserName(int userId) \{ /* generated implementation */ } + public Task DeleteUser(int userId) \{ /* generated implementation */ } + // ... etc +} +``` + + + + + +# Installation + +## NuGet Package Installation + +### Package Manager Console + +```powershell +Install-Package SatorImaging.TDoubles +``` + +### .NET CLI + +```bash +dotnet add package SatorImaging.TDoubles +``` + +### PackageReference (Manual) + +Add the following to your project file (`.csproj`): + +```xml + +``` + + +## System Requirements + +- **.NET Framework**: .NET Standard 2.0 or higher +- **C# Language Version**: C# 7.3 or later +- **Compatible Runtimes**: + - .NET Framework 4.6.1+ + - .NET Core 2.0+ + - .NET 5.0+ + - Unity 2022.3.12f1 or later + + +## Setup and Configuration + +### Basic Setup + +1. Install the NuGet package using one of the methods above +2. Rebuild your project to enable the source generator +3. Create partial classes with the `[Mock]` attribute to generate mocks + +### Project Configuration + +No additional project configuration is required. The source generator automatically activates when the package is installed and will generate mock classes during compilation. + +### Verification + +To verify the installation was successful: + +1. Add a simple mock class to your project: + ```csharp + using TDoubles; + + public interface ITestService + { + string GetMessage(); + } + + [Mock(typeof(ITestService))] + partial class TestServiceMock + { + // Mock implementation will be generated here + } + ``` +2. Build your project +3. Check that no compilation errors occur and the mock class is generated + + +### IDE Support +- **Visual Studio**: Full IntelliSense support for generated mock classes +- **Visual Studio Code**: Works with C# extension +- **JetBrains Rider**: Full support with code completion +- **Command Line**: Works with `dotnet build` and `msbuild` + + + + + +# Basic Usage + +This section provides step-by-step examples to get you started with `TDoubles`. All examples are complete and ready to use in your projects. + + +## Prerequisites + +Before using the TDoubles generator, ensure your mock classes meet these requirements: + +1. **Partial Class**: Mock classes must be declared as `partial` +2. **Mock Attribute**: Apply `[Mock(typeof(TargetType))]` to the partial class +3. **Namespace**: Include `using TDoubles;` +4. **Visibility**: Use any visibility modifier (public, internal, etc.) - generated members will inherit the same visibility + + +## Simple Interface Mocking + +The most common scenario is mocking interfaces for dependency injection testing. + +### Example: User Service Interface + +```csharp +using TDoubles; +using System; +using System.Threading.Tasks; + +// Define your interface +public interface IUserService +{ + string GetUserName(int userId); + Task DeleteUser(int userId); + bool IsUserActive(int userId); +} + +// Create a partial mock class +[Mock(typeof(IUserService))] +partial class UserServiceMock +{ + // The source generator will create the complete implementation here +} + +// Example usage in tests +class Program +{ + static void Main() + { + // Create a concrete implementation for delegation + var realService = new ConcreteUserService(); + + // Create the mock with the real service as the underlying target + var mockService = new UserServiceMock(realService); + + Console.WriteLine("=== Default Behavior (Delegates to Real Service) ==="); + Console.WriteLine($"User Name: {mockService.GetUserName(123)}"); + Console.WriteLine($"Is Active: {mockService.IsUserActive(123)}"); + + Console.WriteLine("\n=== Custom Behavior with Overrides ==="); + + // Override specific methods for testing + mockService.MockOverrides.GetUserName = (userId) => $"MockUser_{userId}"; + mockService.MockOverrides.IsUserActive = (userId) => userId > 100; + + Console.WriteLine($"User Name (Overridden): {mockService.GetUserName(123)}"); + Console.WriteLine($"Is Active (Overridden): {mockService.IsUserActive(50)}"); + Console.WriteLine($"Is Active (Overridden): {mockService.IsUserActive(150)}"); + + // Access the underlying real service if needed + Console.WriteLine($"Real Service: {mockService.MockTarget.GetUserName(123)}"); + } +} + +// Concrete implementation for demonstration +public class ConcreteUserService : IUserService +{ + public string GetUserName(int userId) => $"RealUser_{userId}"; + public async Task DeleteUser(int userId) => await Task.FromResult(true); + public bool IsUserActive(int userId) => true; +} +``` + + +## Class Mocking with Inheritance + +Mock concrete classes to test inheritance scenarios and virtual method overrides. + +### Example: Service Class with Virtual Methods + +```csharp +using TDoubles; +using System; + +// Base service class with virtual methods +public class DatabaseService +{ + public virtual string GetConnectionString() => "Server=localhost;Database=prod;"; + public virtual void SaveData(string data) => Console.WriteLine($"Saving to database: {data}"); + public virtual int GetRecordCount() => 1000; + + // Non-virtual method (will be wrapped but not overridable) + public string GetServiceName() => "DatabaseService"; +} + +// Create mock for the class +[Mock(typeof(DatabaseService))] +partial class DatabaseServiceMock +{ + // Generated implementation will wrap all public methods +} + +// Example usage +class Program +{ + static void Main() + { + // Create real service instance + var realService = new DatabaseService(); + + // Create mock wrapper + var mockService = new DatabaseServiceMock(realService); + + Console.WriteLine("=== Default Behavior ==="); + Console.WriteLine($"Connection: {mockService.GetConnectionString()}"); + Console.WriteLine($"Service Name: {mockService.GetServiceName()}"); + Console.WriteLine($"Record Count: {mockService.GetRecordCount()}"); + mockService.SaveData("test data"); + + Console.WriteLine("\n=== Testing Scenario Overrides ==="); + + // Override for testing scenarios + mockService.MockOverrides.GetConnectionString = () => "Server=testserver;Database=test;"; + mockService.MockOverrides.GetRecordCount = () => 0; // Simulate empty database + mockService.MockOverrides.SaveData = (data) => Console.WriteLine($"TEST MODE: Would save '{data}'"); + + Console.WriteLine($"Test Connection: {mockService.GetConnectionString()}"); + Console.WriteLine($"Test Record Count: {mockService.GetRecordCount()}"); + mockService.SaveData("test data"); + + // Non-virtual methods still work but delegate to original + Console.WriteLine($"Service Name (always delegates): {mockService.GetServiceName()}"); + } +} +``` + + +## Inheritance and Interface Implementation + +Mock classes that both inherit from base classes and implement interfaces. + +### Example: Complex Service Hierarchy + +```csharp +using TDoubles; +using System; + +// Interface definition +public interface INotificationService +{ + void SendNotification(string message); + bool IsServiceAvailable(); +} + +// Base class with virtual methods +public class BaseService +{ + public virtual string GetServiceType() => "Base"; + public virtual void Initialize() => Console.WriteLine("Base initialization"); +} + +// Concrete class that inherits and implements interface +public class EmailService : BaseService, INotificationService +{ + public override string GetServiceType() => "Email"; + public override void Initialize() => Console.WriteLine("Email service initialized"); + + public void SendNotification(string message) => Console.WriteLine($"Email: {message}"); + public bool IsServiceAvailable() => true; +} + +// Mock the concrete class +[Mock(typeof(EmailService))] +partial class EmailServiceMock +{ + // Mocks both inherited methods and interface implementations +} + +// Usage example +class Program +{ + static void Main() + { + var realService = new EmailService(); + var mockService = new EmailServiceMock(realService); + + Console.WriteLine("=== Testing Inherited Methods ==="); + Console.WriteLine($"Service Type: {mockService.GetServiceType()}"); + mockService.Initialize(); + + Console.WriteLine("\n=== Testing Interface Methods ==="); + mockService.SendNotification("Hello World"); + Console.WriteLine($"Available: {mockService.IsServiceAvailable()}"); + + Console.WriteLine("\n=== Testing with Overrides ==="); + + // Override inherited method + mockService.MockOverrides.GetServiceType = () => "MockEmail"; + mockService.MockOverrides.Initialize = () => Console.WriteLine("Mock initialization"); + + // Override interface methods + mockService.MockOverrides.SendNotification = (msg) => Console.WriteLine($"MOCK EMAIL: {msg}"); + mockService.MockOverrides.IsServiceAvailable = () => false; + + Console.WriteLine($"Service Type: {mockService.GetServiceType()}"); + mockService.Initialize(); + mockService.SendNotification("Test Message"); + Console.WriteLine($"Available: {mockService.IsServiceAvailable()}"); + } +} +``` + + + + + +# Advanced Usage + +For advanced scenarios including generic types, static classes, records, structs, and internal member access, see the [Advanced Usage Guide](docs/advanced-usage.md). + + + + + +# Testing Examples + +For comprehensive testing examples with MSTest, NUnit, and performance comparisons, see the [Testing Examples Guide](docs/testing-examples.md). + + + + + + + + + + +# Technical Note + +## `record` and `record struct` + +- Always implements `IEquatable` and `MockOverrides.MockTargetRecord_Equals` + - Note that it is *NOT* `IEquatable` +- `bool Equals(object?)` cannot be overridden + + + + + +# Known Limitations and Unsupported Scenarios + +## Type Parameters of Generic Method + +When method uses method-level type parameter instead of type-level parameter, `MockOverrides` will use `object` instead of method-level type parameter. + +```cs +// Generated mock has type-level parameter T +partial class Mock +{ + // Generated mock method that has T and TMethod type parameter + public TMethod GenericMethod(T input) \{ ... } + + // can be added to this class but it must also be exposed as type-level parameter... + public sealed partial class MockOverrideContainer + { + // type-level parameter T is used but TMethod is shadowed to object + public Func GenericMethod \{ get; set; } + // ~~~~~~ Not TMethod + } +} +``` + +> [!NOTE] +> Generated mock method returns `TMethod` as mock target does. Internally, mock method will cast `object` result from override to `TMethod` when returning value. + + +## Type System Limitations + +**Unsupported Types:** +- Enums (use wrapper classes instead) +- Delegates and function pointers +- Primitive types (`int`, `string`, etc.) +- Static classes with only static constructors +- Abstract classes with pure virtual methods requiring implementation +- `object`, `ValueType`, `Enum` and other special types such as `Span` + + +## Type Constraint Limitations + +**Unsupported Constraints:** +- `where T : default` +- `where T : allows ref struct` + + +## Return Type Limitations + +**Unsupported Type:** +- `ref` return type + + +## Attribute Limitations + +Attributes on type, method, property or etc are not preserved in generated mock. + + +## Method and Property Limitations + +**Unsupported Members:** +- `ref` and `out` parameters in some complex scenarios (?) +- ~~Methods with `__arglist` (variable arguments)~~ +- ~~Explicit interface implementations with name conflicts~~ +- ~~Properties with complex getter/setter accessibility combinations~~ + +**Partial Support:** + +```csharp +public interface IService +{ + // โœ… Fully supported + string GetData(int id); + Task ProcessAsync(string data); + + // โš ๏ธ Limited support - may not override correctly + ref int GetReference(); + void ProcessData(__arglist); +} +``` + + +## Generic Method Limitations + +Some valid type constraint is not transformed correctly. We have no plan to support this edge case of type constraint. + +> Note: `override` method cannot have type constraint except for `class` and `struct`. + +```cs +// Abstract method declaration that returns (M, N?) with where M : N? constraint +public abstract (M t, N? u) TypeArgMappingNullable_Abstract() where M : N?; + +// Expected (valid) return type is (M, N) +public override (M t, N u) TypeArgMappingNullable_Abstract() \{ } + +// But got (M, N?) +public override (M t, N? u) TypeArgMappingNullable_Abstract() \{ } +``` + + + +## Inheritance and Interface Limitations + +**Multiple Interface Implementation:** +- ~~Supported, but explicit interface implementations may have naming conflicts~~ +- Diamond inheritance patterns may cause method resolution issues + +**Virtual Method Overriding:** +- ~~Only `virtual` and `abstract` methods can be overridden in class mocks~~ +- `sealed` methods cannot be overridden (will delegate to original) + + +## Platform and Framework Limitations + +**Framework Support:** +- Requires .NET Standard 2.0 or higher +- Source generators require C# 7.3 or later +- Some advanced C# 11+ features may not be fully supported + +**IDE Integration:** +- IntelliSense may be delayed for newly generated mocks +- Some IDEs may require rebuild to recognize generated code +- Debugging generated code may show optimized/synthetic code + + + + + +# Contributing + +We welcome and appreciate contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or providing feedback, your contributions help make TDoubles better for everyone. + +See [CONTRIBUTING.md](CONTRIBUTING.md) + + + + + +# Code of Conduct + +We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and professional in all interactions. + + + + + +# Support and Community + +## Getting Help + +If you encounter issues not covered in this troubleshooting guide: + +1. **Check the GitHub Issues:** Search existing issues for similar problems +2. **Create a Minimal Reproduction:** Provide the smallest possible code example that demonstrates the issue +3. **Include Build Output:** Share relevant compiler errors and warnings +4. **Specify Environment:** Include .NET version, IDE, and operating system details + +**Support Channels:** +- [GitHub Discussions](https://github.com/sator-imaging/TDoubles/discussions) - Questions and community support +- [GitHub Issues](https://github.com/sator-imaging/TDoubles/issues) - Bug reports and feature requests + + +## Reporting Security Issues + +If you discover a security vulnerability, please report it privately by emailing the maintainers rather than creating a public issue. This allows us to address the issue before it becomes widely known. + + + + + +# Project Information + +## TODO: Help Wanted + +- Missing Tests + - `static` class mocking + - `sealed` overridden methods + - `async` tests + - `event` getter and setter tests + - `readonly struct` tests + - `readonly record struct` tests + - `Tuple` and `ValueTuple` tests + - Property and indexer accessibility tests (e.g., `{ get; private set; }` or etc) +- Missing Features + - `ref` return + - Attribute preservation + - Add proper `` for mock members + - Support for `default` and `allows ref struct` type constraint + - The `default` constraint is valid on override and explicit interface implementation methods only + - Roslyn update is required while keeping Unity engine support + - Emit diagnostic error on type parameters +- Optimization + - Use `ImmutableArray` or `ImmutableList` as possible + - Eliminate inefficient `StringBuilder` use +- Refactor + - Eliminate FP programming techniques + - Transform data model to domain model to encapsulate information and behaviour + - Centralize blueprint-to-C# conversion in domain model to make it consistent, robust and maintainable + - Eliminate duplicate functions, control flows and etc scattered in codebase +- Optional + - ~~New `Mock` attribute option to generate `MockCallCounts` that records the call count of each mock member~~ + - Declare `volatile int` fields + - Increment count by `Interlocked.Increment(ref ...)` method at the beginning of generated mock class member. + + +## Author and Maintainer + +**Sator Imaging** +- GitHub: [@sator-imaging](https://github.com/sator-imaging) +- Project Repository: [sator-imaging/TDoubles](https://github.com/sator-imaging/TDoubles) + + +## Acknowledgments + +We thank all contributors who have helped improve this project through code contributions, bug reports, feature suggestions, and community support. + + +## License + +This project is licensed under the **MIT License**. + +### Third-Party Licenses + +This project uses the following third-party packages: +- **Microsoft.CodeAnalysis.CSharp** (MIT License) +- **Microsoft.CodeAnalysis.Analyzers** (MIT License) + + + + + +--- + +**© 2025 Sator Imaging. All rights reserved.** + +For support, questions, or contributions, please visit our [GitHub repository](https://github.com/sator-imaging/TDoubles). + + +::: + +### About +:::note + +Generating test stubs with mocking for interfaces + + +::: + +## How to use + +### Example (source csproj, source files) + + + + + +This is the CSharp Project that references **SatorImaging.TDoubles** +```xml showLineNumbers {17} + + + + net10.0 + enable + enable + + false + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + true + $(BaseIntermediateOutputPath)\GX + + + + +``` + + + + + + This is the use of **SatorImaging.TDoubles** in *IMyClock.cs* + +```csharp showLineNumbers +namespace MockData; + +public interface IMyClock +{ + public DateTime GetNow(); + public DateTime GetUtcNow(); +} +``` + + + + + This is the use of **SatorImaging.TDoubles** in *TestClock.cs* + +```csharp showLineNumbers + + +namespace TestClock; + +[TDoubles.Mock(typeof(IMyClock))] +public partial class QuickStartRepoStub \{ } + + +[TestClass] +public class TestClock +{ + [TestMethod] + public void TestMyClock() + { + var expectations = new QuickStartRepoStub(); + expectations.MockOverrides.GetNow =()=>(DateTime.Now.AddYears(-1)); + + IMyClock mock = expectations; + var data= mock.GetNow(); + Assert.AreEqual(DateTime.Now.Year -1, data.Year); + + } +} + + + +``` + + + + +### Generated Files + +Those are taken from $(BaseIntermediateOutputPath)\GX + + + + +```csharp showLineNumbers +#nullable enable + +namespace TestClock +{ + partial class QuickStartRepoStub + : global::MockData.IMyClock + { + public QuickStartRepoStub(global::MockData.IMyClock? target = default) + { + _target = target; + MockOverrides = new MockOverrideContainer(); + } + + /// + /// Gets the underlying target instance being mocked. + /// + public global::MockData.IMyClock? MockTarget => _target; + + private readonly global::MockData.IMyClock? _target; + + /// + /// Gets the container for method and property overrides. + /// + public MockOverrideContainer MockOverrides \{ get; } + + public global::System.DateTime GetNow() + { + OnWillMockCall("GetNow"); OnWillMockCall("GetNow", global::System.Array.Empty()); + + global::System.DateTime __STMG_2025_08_25__ = default!; + if (MockOverrides.GetNow != null) { + __STMG_2025_08_25__ = (global::System.DateTime)MockOverrides.GetNow.Invoke(); + } + + return (global::System.DateTime)((MockOverrides.GetNow != null ? __STMG_2025_08_25__ : (_target != null ? ((global::MockData.IMyClock?)_target)!.GetNow() : default))); + } + + public global::System.DateTime GetUtcNow() + { + OnWillMockCall("GetUtcNow"); OnWillMockCall("GetUtcNow", global::System.Array.Empty()); + + global::System.DateTime __STMG_2025_08_25__ = default!; + if (MockOverrides.GetUtcNow != null) { + __STMG_2025_08_25__ = (global::System.DateTime)MockOverrides.GetUtcNow.Invoke(); + } + + return (global::System.DateTime)((MockOverrides.GetUtcNow != null ? __STMG_2025_08_25__ : (_target != null ? ((global::MockData.IMyClock?)_target)!.GetUtcNow() : default))); + } + + public override bool Equals(object? obj) + { + OnWillMockCall("Equals"); OnWillMockCall("Equals", new object?[] \{ obj }); + + bool __STMG_2025_08_25__ = default!; + if (MockOverrides.Equals != null) { + __STMG_2025_08_25__ = (bool)MockOverrides.Equals.Invoke(obj); + } + + return (bool)((MockOverrides.Equals != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.Equals(obj) : default))); + } + + public override int GetHashCode() + { + OnWillMockCall("GetHashCode"); OnWillMockCall("GetHashCode", global::System.Array.Empty()); + + int __STMG_2025_08_25__ = default!; + if (MockOverrides.GetHashCode != null) { + __STMG_2025_08_25__ = (int)MockOverrides.GetHashCode.Invoke(); + } + + return (int)((MockOverrides.GetHashCode != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.GetHashCode() : default))); + } + + public override string? ToString() + { + OnWillMockCall("ToString"); OnWillMockCall("ToString", global::System.Array.Empty()); + + string? __STMG_2025_08_25__ = default!; + if (MockOverrides.ToString != null) { + __STMG_2025_08_25__ = (string?)MockOverrides.ToString.Invoke(); + } + + return (string?)((MockOverrides.ToString != null ? __STMG_2025_08_25__ : (_target != null ? ((object?)_target)!.ToString() : default))); + } + + /// + /// Container for method and property overrides. + /// + public sealed partial class MockOverrideContainer + { + /// + /// Override for global::MockData.IMyClock.GetNow method:
+ DateTime GetNow() + ///
+ public global::System.Func? GetNow \{ get; set; } + + /// + /// Override for global::MockData.IMyClock.GetUtcNow method:
+ DateTime GetUtcNow() + ///
+ public global::System.Func? GetUtcNow \{ get; set; } + + /// + /// Override for object.Equals method:
+ bool Equals(object? obj) + ///
+ public new global::System.Func? Equals \{ get; set; } + + /// + /// Override for object.GetHashCode method:
+ int GetHashCode() + ///
+ public new global::System.Func? GetHashCode \{ get; set; } + + /// + /// Override for object.ToString method:
+ string? ToString() + ///
+ public new global::System.Func? ToString \{ get; set; } + + } + + /// + /// The unified callback invoked before each mock call. + /// + /// The short name of original member without generic type parameters. + partial void OnWillMockCall(string memberName); + + /// + /// The unified callback invoked before each mock call. + /// + /// The short name of original member without generic type parameters. + partial void OnWillMockCall(string memberName, object?[] args); + } +} + +``` + + + + +```csharp showLineNumbers +using System; + +namespace TDoubles +{ + /// + /// Attribute used to mark partial classes for mock generation. + /// The source generator will create a mock implementation that delegates to the target type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)] + internal sealed class MockAttribute : Attribute + { + /// + /// Gets the target type to be mocked. + /// + public Type TargetType \{ get; } + + /// + /// Gets or sets whether to include internal members in the mock. + /// Default is false (public members only). + /// + public bool IncludeInternals \{ get; set; \} = false; + + /// + /// Gets or sets the short names of members to exclude from the generated mock. + /// + public string[] ExcludeMemberShortNames \{ get; set; \} = Array.Empty(); + + /// + /// Initializes a new instance of the MockAttribute with the specified target type. + /// + /// The type to be mocked. Cannot be null. + /// Optional: Short names of members to exclude from the generated mock. + /// Thrown when targetType is null. + public MockAttribute(Type targetType, params string[] excludeMemberShortNames) + { + TargetType = targetType ?? throw new ArgumentNullException(nameof(targetType)); + ExcludeMemberShortNames = excludeMemberShortNames ?? Array.Empty(); + } + } +} +``` + + + + +```csharp showLineNumbers +using System; + +namespace TDoubles +{ + /// + /// Exception thrown when a mock member has no override and returns a non-nullable reference type. + /// This exception provides clear information about which member lacks a mock implementation. + /// + internal class TDoublesException : Exception + { + /// + /// Gets the name of the member that lacks a mock implementation. + /// + public string MemberName \{ get; } + + /// + /// Gets the name of the type that contains the member. + /// + public string TypeName \{ get; } + + /// + /// Initializes a new instance of the TDoublesException with the specified member and type names. + /// + /// The name of the member that lacks a mock implementation. Cannot be null. + /// The name of the type that contains the member. Cannot be null. + public TDoublesException(string memberName, string typeName) + : base($"No mock override provided for member '{memberName}' in type '{typeName}' that returns a non-nullable reference type.") + { + MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName)); + TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + } + + /// + /// Initializes a new instance of the TDoublesException with the specified member name, type name, and custom message. + /// + /// The name of the member that lacks a mock implementation. Cannot be null. + /// The name of the type that contains the member. Cannot be null. + /// The custom error message. + public TDoublesException(string memberName, string typeName, string message) + : base(message) + { + MemberName = memberName ?? throw new ArgumentNullException(nameof(memberName)); + TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + } + } +} +``` + + + + +## Useful + +### Download Example (.NET C#) + +:::tip + +[Download Example project SatorImaging.TDoubles ](/sources/SatorImaging.TDoubles.zip) + +::: + + +### Share SatorImaging.TDoubles + + + +https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles + + + diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/index.md b/v2/rscg_examples_site/docs/RSCG-Examples/index.md index 84a4a91ce..67311e430 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/index.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/index.md @@ -1,7 +1,7 @@ --- sidebar_position: 30 -title: 285 RSCG list by category -description: 285 RSCG list by category +title: 286 RSCG list by category +description: 286 RSCG list by category slug: /rscg-examples --- @@ -1688,7 +1688,7 @@ import DocCardList from '@theme/DocCardList'; ## Tests
- Expand Tests =>examples:9 + Expand Tests =>examples:10 @@ -1734,6 +1734,11 @@ import DocCardList from '@theme/DocCardList'; [TinyBDD.MSTest](/docs/TinyBDD.MSTest) + + + +[SatorImaging.TDoubles](/docs/SatorImaging.TDoubles) +
@@ -2338,6 +2343,8 @@ flowchart LR; Tests--> TinyBDD.MSTest((TinyBDD.MSTest)) + Tests--> SatorImaging.TDoubles((SatorImaging.TDoubles)) + Validator--> validly((validly)) WinAPI--> Com((Com)) diff --git a/v2/rscg_examples_site/docs/about.md b/v2/rscg_examples_site/docs/about.md index 1a11b36bc..3c5a30782 100644 --- a/v2/rscg_examples_site/docs/about.md +++ b/v2/rscg_examples_site/docs/about.md @@ -6,7 +6,7 @@ title: About ## Content You will find here code examples -of 285 Roslyn Source Code Generator (RSCG) +of 286 Roslyn Source Code Generator (RSCG) that can be useful for you. That means, you will write more elegant and concise code - even if the generators code is not always nice to look. ## Are those examples ready for production? diff --git a/v2/rscg_examples_site/docs/indexRSCG.md b/v2/rscg_examples_site/docs/indexRSCG.md index 1ad98a1b0..9728d6125 100644 --- a/v2/rscg_examples_site/docs/indexRSCG.md +++ b/v2/rscg_examples_site/docs/indexRSCG.md @@ -7,9 +7,9 @@ slug: /List-of-RSCG import useBaseUrl from '@docusaurus/useBaseUrl'; -## 285 RSCG with examples in descending chronological order +## 286 RSCG with examples in descending chronological order -This is the list of 285 ( 16 from Microsoft) RSCG with examples +This is the list of 286 ( 16 from Microsoft) RSCG with examples [See by category](/docs/rscg-examples) [See as json](/exports/RSCG.json) [See as Excel](/exports/RSCG.xlsx) @@ -20,6 +20,7 @@ This is the list of 285 ( 16 from Microsoft) RSCG with examples | No | Name | Date | Category | | --------- | ----- | ---- | -------- | +|286| [SatorImaging.TDoubles by Sator Imaging ](/docs/SatorImaging.TDoubles)|2026-08-27 => 27 August 2026 | [Tests](/docs/Categories/Tests) | |285| [PropertyResolvers by Tom Biddulph ](/docs/PropertyResolvers)|2026-08-26 => 26 August 2026 | [EnhancementProject](/docs/Categories/EnhancementProject) | |284| [Pinecone.TypedPath by Nick Pinecone ](/docs/Pinecone.TypedPath)|2026-08-24 => 24 August 2026 | [FilesToCode](/docs/Categories/FilesToCode) | |283| [IncrementalSourceGenerator.BuilderPattern by Martin Barrett Nielsen ](/docs/IncrementalSourceGenerator.BuilderPattern)|2026-08-23 => 23 August 2026 | [Builder](/docs/Categories/Builder) | diff --git a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js index e0f5467ce..e97576692 100644 --- a/v2/rscg_examples_site/src/components/HomepageFeatures/index.js +++ b/v2/rscg_examples_site/src/components/HomepageFeatures/index.js @@ -4,7 +4,7 @@ import styles from './styles.module.css'; const FeatureList = [ { -title: '285 Examples (16 from MSFT)', +title: '286 Examples (16 from MSFT)', Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default, description: ( <> diff --git a/v2/rscg_examples_site/static/exports/RSCG.json b/v2/rscg_examples_site/static/exports/RSCG.json index 47748093e..7ba28fd85 100644 --- a/v2/rscg_examples_site/static/exports/RSCG.json +++ b/v2/rscg_examples_site/static/exports/RSCG.json @@ -2281,6 +2281,14 @@ "Source": "https://github.com/tombiddulph/PropertyResolvers", "Category": "EnhancementProject", "AddedOn": "2026-08-26T00:00:00" + }, + { + "Name": "SatorImaging.TDoubles", + "Link": "https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles", + "NuGet": "https://www.nuget.org/packages/SatorImaging.TDoubles/", + "Source": "https://github.com/sator-imaging/TDoubles", + "Category": "Tests", + "AddedOn": "2026-08-27T00:00:00" } ] } \ No newline at end of file diff --git a/v2/rscg_examples_site/static/exports/RSCG.xlsx b/v2/rscg_examples_site/static/exports/RSCG.xlsx index 4db423cb9dd72da74f7aabd1b1632eaa059cfeb0..0bdeccf4b66eafda29700301d0d0b9182273abc0 100644 GIT binary patch delta 878 zcmdm5^01^nz?+#xgn@y9gF#@2eC*5J90I$6JS9d327Vx2k)vOpUzDAcpP#K)k(<-o ze~{~tfq+ZB>YLbLR~N5ney&HM`xTZ+OXqA-mEJo4{i+q=`J0VTe^?gPzcAd~=Z)qy zgV|yW45G4XZLSxb&D+#e8r=JI?xm+LC4ryhcgkeHs4tT9T6?ohg2zAefX2+lB0kfM z=bG|!`kt0e;@dk<{AdvSV`ZD)aeDPryVMeR+aIT~zk2#6`XJ*}Fa1XahjvCQT4}d( zC#zHxFR8aUVVPg({Y}c?nYqWIdnq^1%$1jzlBe~zS?K4@+cpfS9z7M`@M#{CslDOd1x7!Z#1%wzkUQ1Iw_`y`ZnD5fsq+hX;M}9pFX8p7NzL6bEQ+b{J z)t>)rWB;w;yENVY|NH;^Fvl=}L!IC4jf@7+F(Awh4E6Y;)SS)6rreDDspf{J78b@P z7G{JdkL!A+g7uo#UNRzqn@V0vJz?r{x#^bU zIbn*KWpta|%J3CYBCb3oFD`XHQ2+UXVc-3GX1hUB6bX|HEyb*CO(8K*tgi=*hhlUCK|y~DXv$t-WJw`R1G4%iAGFj2IqZ$4 W4ATPh$rH>)C(Bz2u${I5DFXnp6-Af; delta 851 zcmaD@vbCf>z?+#xgn@y9gJIP=`Pj5KOXlqd@{|}E82Ev7MUH-Xeo=N(etx!IMQ%=S z|LML%20X6ys-NV9<~6O-<(?oA{fyh`)rAhVb&nYS))?8OZF|$VUibB#BI6%^cMLWy z4mg;y#v#Gr%8r6{4`#i|V4b~7rpx;1>CP8QpK2c@t*Bq%yHbBi%H>7ZZI;P7-;3;u zTW)h~^Ao#8c{^Nv_Dv36I_*-n_=E5Fji1^JIaaTE@MH7c<_fiTwbuak zC7Y$0C7PreO|CSP=Kxu%Qo2lX@&YrBdfU9i20U)-l?v8t9`8#O58RY;QtAoQDxaHf zIi3@y?BASvVq#V6D%Zdx4()n#IqFp8O@@dqRx-~0`WlcFb|mEELj*Bc!7xy0p0+az$92)Qkj!l3`&3j-i%Bl42blK z8fyrR3=CT*=UJ*pfQ$!4g9gxe5avc!!XRkMzz|=Qnp3Q=2O`l82gS}UpoYCbjZ!EY clP15l)MQ$ Date: Mon, 31 Aug 2026 23:03:50 +0300 Subject: [PATCH 2/3] zip --- v2/book/examples/SatorImaging.TDoubles.html | 69 ++++++++++++++++++ v2/book/list.html | 6 +- v2/book/pandocHTML.yaml | 1 + v2/docFind.json | 8 +- .../static/sources/SatorImaging.TDoubles.zip | Bin 0 -> 1791 bytes 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 v2/book/examples/SatorImaging.TDoubles.html create mode 100644 v2/rscg_examples_site/static/sources/SatorImaging.TDoubles.zip diff --git a/v2/book/examples/SatorImaging.TDoubles.html b/v2/book/examples/SatorImaging.TDoubles.html new file mode 100644 index 000000000..dbd0a58b9 --- /dev/null +++ b/v2/book/examples/SatorImaging.TDoubles.html @@ -0,0 +1,69 @@ + +

RSCG nr 286 : SatorImaging.TDoubles

+ +

Info

+Nuget : https://www.nuget.org/packages/SatorImaging.TDoubles/ + +

You can find more details at : https://github.com/sator-imaging/TDoubles

+ +

Author :Sator Imaging

+ +

Source: https://github.com/sator-imaging/TDoubles

+ +

About

+ +Generating test stubs with mocking for interfaces + +

+ How to use +

+

+ Add reference to the SatorImaging.TDoubles in the csproj +

+ + +

This was for me the starting code

+ +
+ I have coded the file IMyClock.cs +
+ +
+ +
+ I have coded the file TestClock.cs +
+ +
+

And here are the generated files

+ +
+ The file generated is TestClock.QuickStartRepoStub.g.cs +
+ + +
+ The file generated is _ TDoubles _ MockAttribute.g.cs +
+ + +
+ The file generated is _ TDoubles _ TDoublesException.g.cs +
+ + +

+ You can download the code and this page as pdf from + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/SatorImaging.TDoubles + +

+ + +

+ You can see the whole list at + + https://ignatandrei.github.io/RSCG_Examples/v2/docs/List-of-RSCG + +

+ diff --git a/v2/book/list.html b/v2/book/list.html index 717346c51..158b1c9f2 100644 --- a/v2/book/list.html +++ b/v2/book/list.html @@ -17,7 +17,7 @@

-This is the list of 285 RSCG with examples => +This is the list of 286 RSCG with examples =>

@@ -1166,6 +1166,10 @@

+ + + +
285 PropertyResolvers
286SatorImaging.TDoubles
diff --git a/v2/book/pandocHTML.yaml b/v2/book/pandocHTML.yaml index d6de7a8d7..ebec810a2 100644 --- a/v2/book/pandocHTML.yaml +++ b/v2/book/pandocHTML.yaml @@ -299,6 +299,7 @@ input-files: - examples/IncrementalSourceGenerator.BuilderPattern.html - examples/Pinecone.TypedPath.html - examples/PropertyResolvers.html +- examples/SatorImaging.TDoubles.html # or you may use input-file: with a single value # defaults: diff --git a/v2/docFind.json b/v2/docFind.json index 82e178172..4f3c3ee43 100644 --- a/v2/docFind.json +++ b/v2/docFind.json @@ -1707,6 +1707,12 @@ "title": "PropertyResolvers", "category": "EnhancementProject", "href": "/RSCG_Examples/v2/docs/PropertyResolvers/", - "body": "A source generator that creates property resolver classes for types with\n matching properties. Use assembly-level attributes to generate resolvers that can\n retrieve property values from objects at runtime." + "body": "A source generator that creates property resolver classes for types with\r\n matching properties. Use assembly-level attributes to generate resolvers that can\r\n retrieve property values from objects at runtime." + }, + { + "title": "SatorImaging.TDoubles", + "category": "Tests", + "href": "/RSCG_Examples/v2/docs/SatorImaging.TDoubles/", + "body": "Incremental source generator for creating mock wrapper classes." } ] \ No newline at end of file diff --git a/v2/rscg_examples_site/static/sources/SatorImaging.TDoubles.zip b/v2/rscg_examples_site/static/sources/SatorImaging.TDoubles.zip new file mode 100644 index 0000000000000000000000000000000000000000..c270d6499c2a5b9402db224990aae14cd1ee8b51 GIT binary patch literal 1791 zcmWIWW@Zs#U|`^2I1=g?3lD02KQ=^0prh1=>>u!~#(J^gVqmopV5zCKp%luda_v%F8NrOe$K{lwVi0 z%{$3+k*U78ez;?1kYCy+r?fcFtUxJ`YnQiQvSeS;G=24~>E4VS3$z-nf}D+tgGKKI zd;j@#+SFuip;!AfmBS%dn9!V1P;KAY3AFkK5Q72&hE1&*!y;)YdQnih(ip}iT3!#9o^LDaY&OA9&b>Y#Ol`oH$t*q?yv|MTSE8|eW z(YmFUmI{?SjCL^GaQyW`@6l7H?nWEtC&;0fwM{YLco`WOegd%=(CHzm#U@(m=yk+EV6XTm{$ig4x^H%dXsqjK*V?tWMX#^mUG(hS!Qkab5??ca>v7Ak-QI3o!znChfN=2b6^X1Cl@@#nZy>diH~LXP;`f6G`qDUqf8 zh5Q%6qEnx;cRiHTzM3GlV9SJ0tUF6fwx&%#`t0`k6AL8!YA&ipS53R~+2+%-KJNO~ z;*8p;h&_K_RP6m?IHTd}FRg1G>-Xm!5?kh4!;DCNSGLN>wp5wf90mqnHwy!U6wUwy zrGUA?w)wXWMCQmU{BIPn+QOZ>T*E3taDrLqLdBJV){gBuJYU~zTISJMQ!?@2uL(S> zPptZUwYA~h+`c`JH_nj$%WW~qb+6?)Jr?s@uS74M-mLmHAWTk9Mou88B8r>seVW_u zZ9Eg$r=MBFc%C==!FIFRL5zOKN@AWIc6=1>tIaB2k=hz{TS#R0)PN_<78CX7PD#9X z(dxlN56AQ8B=5WA99_a2)bDXTvgzoPO)ig>R`D zF7NQe2D=u>e^$Ppu;#)mi`g~H=D)B|{;9o1?Bo9pbFQp$HL#tlT;-ykf9>VFNB^hn zxYsnXVM?}UZN{$pMg4KlOO3dn{a+)s?VGso^yRmHc5nLIvtqsfw34LPN2V#xiQvxg zR!iNn-0q6uWid`Ozl9kIUU_pTe4V1vmh{?wXBY=p_@R7jRUgC3@5kTIt;m?MNy(tr zwd~8JjWsWHN|(n^R^GH73_VvmB!gpQ$|0>%T2NquA__J#E zwb@3-0SPrWUn@7vc=eA*k?YO;+Tt5lbBzB)?yLUa8o8pTY*}Me#i_?14xfGFK5@T> z;JJOzdtG(*9;!w}J*cE}{u$ft28wbZ7DkEs(BjOz^kQJf@jidj-|I}EhVHpD-r*nM znV$Q!j-KxsPCZTElV`4Td+O?Oo;-hE^BVWr(>jbF8@kq3?z|NE<(Yc=Zk1^&Oh*|5 zycwB9z$FXzA{J;j5 Date: Mon, 31 Aug 2026 23:06:33 +0300 Subject: [PATCH 3/3] md --- v2/RSCGExamplesData/GeneratorDataRec.json | 2 +- .../RSCG-Examples/SatorImaging.TDoubles.md | 122 +++++++++--------- 2 files changed, 62 insertions(+), 62 deletions(-) diff --git a/v2/RSCGExamplesData/GeneratorDataRec.json b/v2/RSCGExamplesData/GeneratorDataRec.json index 0d35fbd96..4e0c4b142 100644 --- a/v2/RSCGExamplesData/GeneratorDataRec.json +++ b/v2/RSCGExamplesData/GeneratorDataRec.json @@ -1732,7 +1732,7 @@ "ID": "SatorImaging.TDoubles", "Category": 13, "dtStart": "2026-08-27T00:00:00", - "show": true + "show": true } ] \ No newline at end of file diff --git a/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md b/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md index 3f7622416..ffa86267f 100644 --- a/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md +++ b/v2/rscg_examples_site/docs/RSCG-Examples/SatorImaging.TDoubles.md @@ -54,11 +54,11 @@ Sator Imaging   [![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/sator-imaging/TDoubles) -[๐Ÿ‡บ๐Ÿ‡ธ English](./README.md) +[๐Ÿ‡บ๐Ÿ‡ธ English](https://github.com/sator-imaging/TDoubles/README.md)   โ˜   -[๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž็‰ˆ](./README.ja.md) +[๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž็‰ˆ](https://github.com/sator-imaging/TDoubles/README.ja.md)   โ˜   -[๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡็‰ˆ](./README.zh-CN.md) +[๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡็‰ˆ](https://github.com/sator-imaging/TDoubles/README.zh-CN.md) @@ -70,7 +70,7 @@ Sator Imaging -## โœจ Comparison with Traditional Mocking Frameworks +###### โœจ Comparison with Traditional Mocking Frameworks | Feature | TDoubles | Traditional Frameworks (Moq, NSubstitute) | |---------|---------------------------|-------------------------------------------| @@ -84,7 +84,7 @@ Sator Imaging -# โšก Quick Start +### โšก Quick Start Apply the `[Mock]` attribute to a partial class, and the generator handles the rest. @@ -150,7 +150,7 @@ var fake = new FooFake(new ConcreteFoo()); -## Generic Type Support +###### Generic Type Support `TDoubles` provides support for generic type mocking on both unbound and closed constructed generics. @@ -165,7 +165,7 @@ partial class DictionaryMock {} ``` -## Efficient Callback Support +###### Efficient Callback Support There are efficient extension points to implement custom callback for each mock member call. @@ -211,7 +211,7 @@ partial class ListSpy // ๐Ÿ•ต < Investigate suspects! ``` -## `Mock` Attribute Options +###### `Mock` Attribute Options There are options to select generated mock members. @@ -233,12 +233,12 @@ partial class FooMockWithoutToStringOverride -# Introduction +### Introduction The generator works by analyzing types marked with the `[Mock]` attribute and generating corresponding mock classes that delegate to the original implementation while providing override capabilities through a simple, strongly-typed API. This approach eliminates the performance overhead of reflection-based mocking while maintaining full type safety and IntelliSense support. -## Key Benefits +###### Key Benefits - **Zero Runtime Overhead**: Mock classes are generated at compile-time, eliminating reflection costs and improving test execution performance - **Full Type Safety**: Generated mocks provide complete IntelliSense support, compile-time checking, and refactoring safety @@ -249,7 +249,7 @@ The generator works by analyzing types marked with the `[Mock]` attribute and ge - **Clean Generated Code**: Produces human-readable, debuggable mock implementations that integrate seamlessly with your codebase -## Use Cases +###### Use Cases TDoubles generator excels in scenarios where you need: @@ -261,14 +261,14 @@ TDoubles generator excels in scenarios where you need: - **Internal API Testing**: Testing internal members without making them public -## How It Works +###### How It Works 1. **Mark Target Types**: Apply the `[Mock(typeof(TargetType))]` attribute to a partial class 2. **Compile-Time Generation**: The source generator analyzes your target type and creates a mock implementation 3. **Delegate with Overrides**: Generated mocks delegate to the original instance while providing `MockOverrides` for custom behavior 4. **Test with Confidence**: Use the generated mock in your tests with full type safety and performance -### Delegation Logic +######### Delegation Logic Here is pseudo code of delegation. Actual code is more complicated as need to support `ref` and `out` parameter modifiers. @@ -282,7 +282,7 @@ public string GetData(int id) } ``` -### Generated Mock Structure +######### Generated Mock Structure When you create a mock class, the generator adds several members: @@ -317,23 +317,23 @@ partial class UserServiceMock -# Installation +### Installation -## NuGet Package Installation +###### NuGet Package Installation -### Package Manager Console +######### Package Manager Console ```powershell Install-Package SatorImaging.TDoubles ``` -### .NET CLI +######### .NET CLI ```bash dotnet add package SatorImaging.TDoubles ``` -### PackageReference (Manual) +######### PackageReference (Manual) Add the following to your project file (`.csproj`): @@ -342,7 +342,7 @@ Add the following to your project file (`.csproj`): ``` -## System Requirements +###### System Requirements - **.NET Framework**: .NET Standard 2.0 or higher - **C# Language Version**: C# 7.3 or later @@ -353,19 +353,19 @@ Add the following to your project file (`.csproj`): - Unity 2022.3.12f1 or later -## Setup and Configuration +###### Setup and Configuration -### Basic Setup +######### Basic Setup 1. Install the NuGet package using one of the methods above 2. Rebuild your project to enable the source generator 3. Create partial classes with the `[Mock]` attribute to generate mocks -### Project Configuration +######### Project Configuration No additional project configuration is required. The source generator automatically activates when the package is installed and will generate mock classes during compilation. -### Verification +######### Verification To verify the installation was successful: @@ -388,7 +388,7 @@ To verify the installation was successful: 3. Check that no compilation errors occur and the mock class is generated -### IDE Support +######### IDE Support - **Visual Studio**: Full IntelliSense support for generated mock classes - **Visual Studio Code**: Works with C# extension - **JetBrains Rider**: Full support with code completion @@ -398,12 +398,12 @@ To verify the installation was successful: -# Basic Usage +### Basic Usage This section provides step-by-step examples to get you started with `TDoubles`. All examples are complete and ready to use in your projects. -## Prerequisites +###### Prerequisites Before using the TDoubles generator, ensure your mock classes meet these requirements: @@ -413,11 +413,11 @@ Before using the TDoubles generator, ensure your mock classes meet these require 4. **Visibility**: Use any visibility modifier (public, internal, etc.) - generated members will inherit the same visibility -## Simple Interface Mocking +###### Simple Interface Mocking The most common scenario is mocking interfaces for dependency injection testing. -### Example: User Service Interface +######### Example: User Service Interface ```csharp using TDoubles; @@ -479,11 +479,11 @@ public class ConcreteUserService : IUserService ``` -## Class Mocking with Inheritance +###### Class Mocking with Inheritance Mock concrete classes to test inheritance scenarios and virtual method overrides. -### Example: Service Class with Virtual Methods +######### Example: Service Class with Virtual Methods ```csharp using TDoubles; @@ -542,11 +542,11 @@ class Program ``` -## Inheritance and Interface Implementation +###### Inheritance and Interface Implementation Mock classes that both inherit from base classes and implement interfaces. -### Example: Complex Service Hierarchy +######### Example: Complex Service Hierarchy ```csharp using TDoubles; @@ -621,17 +621,17 @@ class Program -# Advanced Usage +### Advanced Usage -For advanced scenarios including generic types, static classes, records, structs, and internal member access, see the [Advanced Usage Guide](docs/advanced-usage.md). +For advanced scenarios including generic types, static classes, records, structs, and internal member access, see the [Advanced Usage Guide](https://github.com/sator-imaging/TDoubles/docs/advanced-usage.md). -# Testing Examples +### Testing Examples -For comprehensive testing examples with MSTest, NUnit, and performance comparisons, see the [Testing Examples Guide](docs/testing-examples.md). +For comprehensive testing examples with MSTest, NUnit, and performance comparisons, see the [Testing Examples Guide](https://github.com/sator-imaging/TDoubles/docs/testing-examples.md). @@ -642,9 +642,9 @@ For comprehensive testing examples with MSTest, NUnit, and performance compariso -# Technical Note +### Technical Note -## `record` and `record struct` +###### `record` and `record struct` - Always implements `IEquatable` and `MockOverrides.MockTargetRecord_Equals` - Note that it is *NOT* `IEquatable` @@ -654,9 +654,9 @@ For comprehensive testing examples with MSTest, NUnit, and performance compariso -# Known Limitations and Unsupported Scenarios +### Known Limitations and Unsupported Scenarios -## Type Parameters of Generic Method +###### Type Parameters of Generic Method When method uses method-level type parameter instead of type-level parameter, `MockOverrides` will use `object` instead of method-level type parameter. @@ -681,7 +681,7 @@ partial class Mock > Generated mock method returns `TMethod` as mock target does. Internally, mock method will cast `object` result from override to `TMethod` when returning value. -## Type System Limitations +###### Type System Limitations **Unsupported Types:** - Enums (use wrapper classes instead) @@ -692,25 +692,25 @@ partial class Mock - `object`, `ValueType`, `Enum` and other special types such as `Span` -## Type Constraint Limitations +###### Type Constraint Limitations **Unsupported Constraints:** - `where T : default` - `where T : allows ref struct` -## Return Type Limitations +###### Return Type Limitations **Unsupported Type:** - `ref` return type -## Attribute Limitations +###### Attribute Limitations Attributes on type, method, property or etc are not preserved in generated mock. -## Method and Property Limitations +###### Method and Property Limitations **Unsupported Members:** - `ref` and `out` parameters in some complex scenarios (?) @@ -734,7 +734,7 @@ public interface IService ``` -## Generic Method Limitations +###### Generic Method Limitations Some valid type constraint is not transformed correctly. We have no plan to support this edge case of type constraint. @@ -753,7 +753,7 @@ public override (M t, N? u) TypeArgMappingNullable_Abstract() \{ } -## Inheritance and Interface Limitations +###### Inheritance and Interface Limitations **Multiple Interface Implementation:** - ~~Supported, but explicit interface implementations may have naming conflicts~~ @@ -764,7 +764,7 @@ public override (M t, N? u) TypeArgMappingNullable_Abstract() \{ } - `sealed` methods cannot be overridden (will delegate to original) -## Platform and Framework Limitations +###### Platform and Framework Limitations **Framework Support:** - Requires .NET Standard 2.0 or higher @@ -780,17 +780,17 @@ public override (M t, N? u) TypeArgMappingNullable_Abstract() \{ } -# Contributing +### Contributing We welcome and appreciate contributions from the community! Whether you're fixing bugs, adding features, improving documentation, or providing feedback, your contributions help make TDoubles better for everyone. -See [CONTRIBUTING.md](CONTRIBUTING.md) +See [CONTRIBUTING.md](https://github.com/sator-imaging/TDoubles/CONTRIBUTING.md) -# Code of Conduct +### Code of Conduct We are committed to providing a welcoming and inclusive environment for all contributors. Please be respectful and professional in all interactions. @@ -798,9 +798,9 @@ We are committed to providing a welcoming and inclusive environment for all cont -# Support and Community +### Support and Community -## Getting Help +###### Getting Help If you encounter issues not covered in this troubleshooting guide: @@ -814,7 +814,7 @@ If you encounter issues not covered in this troubleshooting guide: - [GitHub Issues](https://github.com/sator-imaging/TDoubles/issues) - Bug reports and feature requests -## Reporting Security Issues +###### Reporting Security Issues If you discover a security vulnerability, please report it privately by emailing the maintainers rather than creating a public issue. This allows us to address the issue before it becomes widely known. @@ -822,9 +822,9 @@ If you discover a security vulnerability, please report it privately by emailing -# Project Information +### Project Information -## TODO: Help Wanted +###### TODO: Help Wanted - Missing Tests - `static` class mocking @@ -857,23 +857,23 @@ If you discover a security vulnerability, please report it privately by emailing - Increment count by `Interlocked.Increment(ref ...)` method at the beginning of generated mock class member. -## Author and Maintainer +###### Author and Maintainer **Sator Imaging** - GitHub: [@sator-imaging](https://github.com/sator-imaging) - Project Repository: [sator-imaging/TDoubles](https://github.com/sator-imaging/TDoubles) -## Acknowledgments +###### Acknowledgments We thank all contributors who have helped improve this project through code contributions, bug reports, feature suggestions, and community support. -## License +###### License This project is licensed under the **MIT License**. -### Third-Party Licenses +######### Third-Party Licenses This project uses the following third-party packages: - **Microsoft.CodeAnalysis.CSharp** (MIT License)