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..4e0c4b142 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/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/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..ffa86267f --- /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](https://github.com/sator-imaging/TDoubles/README.md) +  โ˜   +[๐Ÿ‡ฏ๐Ÿ‡ต ๆ—ฅๆœฌ่ชž็‰ˆ](https://github.com/sator-imaging/TDoubles/README.ja.md) +  โ˜   +[๐Ÿ‡จ๐Ÿ‡ณ ็ฎ€ไฝ“ไธญๆ–‡็‰ˆ](https://github.com/sator-imaging/TDoubles/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](https://github.com/sator-imaging/TDoubles/docs/advanced-usage.md). + + + + + +### Testing Examples + +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). + + + + + + + + + + +### 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](https://github.com/sator-imaging/TDoubles/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 4db423cb9..0bdeccf4b 100644 Binary files a/v2/rscg_examples_site/static/exports/RSCG.xlsx and b/v2/rscg_examples_site/static/exports/RSCG.xlsx differ 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 000000000..c270d6499 Binary files /dev/null and b/v2/rscg_examples_site/static/sources/SatorImaging.TDoubles.zip differ