Skip to main content
Testing is essential for ensuring your Intent Architect modules work correctly across different scenarios and metadata configurations.

Testing Strategy

A comprehensive testing approach includes:
  • Unit Tests: Test individual template methods and helpers
  • Integration Tests: Test entire template outputs
  • Scenario Tests: Test with various metadata configurations
  • Regression Tests: Ensure changes don’t break existing functionality
  • Manual Testing: Verify visual output and edge cases

Test Project Setup

1
Create Test Project
2
Add a test project to your solution:
3
dotnet new xunit -n MyModule.Tests
dotnet sln add MyModule.Tests/MyModule.Tests.csproj
4
Add Dependencies
5
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
    <PackageReference Include="xunit" Version="2.6.2" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.4" />
    <PackageReference Include="Moq" Version="4.20.70" />
    <PackageReference Include="FluentAssertions" Version="6.12.0" />
    <PackageReference Include="Verify.Xunit" Version="22.7.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyModule\MyModule.csproj" />
  </ItemGroup>
</Project>
6
Configure Test Infrastructure
7
using Xunit;

// Disable test parallelization if tests share resources
[assembly: CollectionBehavior(DisableTestParallelization = true)]

Unit Testing Templates

Testing Template Methods

Test individual methods in templates:
EntityTemplateTests.cs
using Xunit;
using FluentAssertions;
using Moq;
using MyModule.Templates.Entity;
using MyModule.Api;

public class EntityTemplateTests
{
    [Fact]
    public void GenerateProperties_CreatesPropertyForEachModelProperty()
    {
        // Arrange
        var mockModel = CreateMockClassModel(
            "Customer",
            new[] { ("Id", "int"), ("Name", "string"), ("Email", "string") });

        var template = new EntityTemplate(
            CreateMockOutputTarget(),
            mockModel.Object);

        // Act
        var result = template.GenerateProperties();

        // Assert
        result.Should().Contain("public int Id { get; set; }");
        result.Should().Contain("public string Name { get; set; }");
        result.Should().Contain("public string Email { get; set; }");
    }

    [Fact]
    public void GenerateProperties_SkipsPropertiesWithIgnoreStereotype()
    {
        // Arrange
        var mockModel = CreateMockClassModel("Customer");
        var ignoredProperty = CreateMockProperty("Internal", "string");
        ignoredProperty.Setup(p => p.HasStereotype("Ignore"))
            .Returns(true);

        mockModel.Setup(m => m.Properties)
            .Returns(new[] { ignoredProperty.Object });

        var template = new EntityTemplate(
            CreateMockOutputTarget(),
            mockModel.Object);

        // Act
        var result = template.GenerateProperties();

        // Assert
        result.Should().NotContain("Internal");
    }

    [Theory]
    [InlineData("int", "int")]
    [InlineData("string", "string")]
    [InlineData("DateTime", "DateTime")]
    [InlineData("Guid", "Guid")]
    public void GetTypeName_ReturnsCorrectTypeName(
        string inputType, 
        string expectedType)
    {
        // Arrange
        var template = CreateTemplate();

        // Act
        var result = template.GetTypeName(inputType);

        // Assert
        result.Should().Be(expectedType);
    }

    private Mock<ClassModel> CreateMockClassModel(
        string name, 
        (string name, string type)[] properties = null)
    {
        var mock = new Mock<ClassModel>();
        mock.Setup(m => m.Name).Returns(name);
        
        if (properties != null)
        {
            var propertyMocks = properties
                .Select(p => CreateMockProperty(p.name, p.type).Object)
                .ToList();
            mock.Setup(m => m.Properties).Returns(propertyMocks);
        }
        else
        {
            mock.Setup(m => m.Properties).Returns(new List<PropertyModel>());
        }

        return mock;
    }

    private Mock<PropertyModel> CreateMockProperty(string name, string type)
    {
        var mock = new Mock<PropertyModel>();
        mock.Setup(p => p.Name).Returns(name);
        mock.Setup(p => p.Type).Returns(type);
        mock.Setup(p => p.HasStereotype(It.IsAny<string>())).Returns(false);
        return mock;
    }

    private Mock<IOutputTarget> CreateMockOutputTarget()
    {
        var mock = new Mock<IOutputTarget>();
        mock.Setup(o => o.GetNamespace()).Returns("TestNamespace");
        return mock;
    }

    private EntityTemplate CreateTemplate()
    {
        return new EntityTemplate(
            CreateMockOutputTarget().Object,
            CreateMockClassModel("TestClass").Object);
    }
}

Testing Helper Classes

EntityHelperTests.cs
using Xunit;
using FluentAssertions;

public class EntityHelperTests
{
    [Fact]
    public void IsAggregateRoot_ReturnsTrueWhenStereotypePresent()
    {
        // Arrange
        var model = CreateMockClassModel("Order");
        model.Setup(m => m.HasStereotype("Aggregate Root")).Returns(true);

        // Act
        var result = EntityHelper.IsAggregateRoot(model.Object);

        // Assert
        result.Should().BeTrue();
    }

    [Theory]
    [InlineData("Customer", "Customers")]
    [InlineData("Order", "Orders")]
    [InlineData("Person", "People")]
    [InlineData("Address", "Addresses")]
    public void GetTableName_PluralizesEntityName(
        string entityName, 
        string expectedTableName)
    {
        // Arrange
        var model = CreateMockClassModel(entityName);

        // Act
        var result = EntityHelper.GetTableName(model.Object);

        // Assert
        result.Should().Be(expectedTableName);
    }

    [Fact]
    public void GetTableName_UsesStereotypeValueWhenPresent()
    {
        // Arrange
        var model = CreateMockClassModel("Customer");
        model.Setup(m => m.GetStereotypeProperty<string>(
            "Entity", "TableName"))
            .Returns("tbl_customers");

        // Act
        var result = EntityHelper.GetTableName(model.Object);

        // Assert
        result.Should().Be("tbl_customers");
    }
}

Snapshot Testing

Use Verify to test entire template outputs:

Basic Snapshot Test

EntityTemplateSnapshotTests.cs
using Xunit;
using VerifyXunit;
using MyModule.Templates.Entity;

[UsesVerify]
public class EntityTemplateSnapshotTests
{
    [Fact]
    public async Task EntityTemplate_GeneratesCorrectOutput()
    {
        // Arrange
        var model = CreateTestModel("Customer", new[]
        {
            ("Id", "int"),
            ("Name", "string"),
            ("Email", "string")
        });

        var template = new EntityTemplate(
            CreateOutputTarget(),
            model);

        // Act
        var output = template.TransformText();

        // Assert
        await Verifier.Verify(output)
            .UseDirectory("Snapshots");
    }

    [Fact]
    public async Task EntityTemplate_WithRelationships_GeneratesCorrectOutput()
    {
        // Arrange
        var model = CreateTestModelWithRelationships();
        var template = new EntityTemplate(
            CreateOutputTarget(),
            model);

        // Act
        var output = template.TransformText();

        // Assert
        await Verifier.Verify(output)
            .UseDirectory("Snapshots")
            .UseFileName("EntityTemplate.WithRelationships");
    }
}

Snapshot File Example

The first run creates a snapshot file:
Snapshots/EntityTemplateSnapshotTests.EntityTemplate_GeneratesCorrectOutput.verified.txt
using System;

namespace TestNamespace.Domain.Entities
{
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}
Subsequent runs compare against this snapshot.

Testing Decorators

Unit Testing Decorators

LoggingDecoratorTests.cs
using Xunit;
using FluentAssertions;
using Moq;
using Intent.Modules.Common.CSharp.Builder;

public class LoggingDecoratorTests
{
    [Fact]
    public void Decorator_AddsLoggingField()
    {
        // Arrange
        var mockTemplate = new Mock<IServiceTemplate>();
        var classBuilder = new CSharpClass("TestService");
        mockTemplate.Setup(t => t.Class).Returns(classBuilder);

        var decorator = new LoggingDecorator(
            mockTemplate.Object,
            CreateMockApplication().Object);

        // Act
        decorator.BeforeTemplateExecution();

        // Assert
        classBuilder.Fields
            .Should().ContainSingle(f => 
                f.Name == "_logger" && 
                f.Type == "ILogger");
    }

    [Fact]
    public void Decorator_AddsLoggerParameter()
    {
        // Arrange
        var mockTemplate = new Mock<IServiceTemplate>();
        var classBuilder = new CSharpClass("TestService");
        var constructor = classBuilder.AddConstructor();
        
        mockTemplate.Setup(t => t.Class).Returns(classBuilder);
        mockTemplate.Setup(t => t.Constructor).Returns(constructor);

        var decorator = new LoggingDecorator(
            mockTemplate.Object,
            CreateMockApplication().Object);

        // Act
        decorator.BeforeTemplateExecution();

        // Assert
        constructor.Parameters
            .Should().ContainSingle(p => 
                p.Name == "logger" && 
                p.Type == "ILogger");
    }

    [Fact]
    public void Decorator_AddsLoggingToMethods()
    {
        // Arrange
        var mockTemplate = new Mock<IServiceTemplate>();
        var classBuilder = new CSharpClass("TestService");
        classBuilder.AddMethod("void", "ProcessOrder");
        
        mockTemplate.Setup(t => t.Class).Returns(classBuilder);

        var decorator = new LoggingDecorator(
            mockTemplate.Object,
            CreateMockApplication().Object);

        // Act
        decorator.BeforeTemplateExecution();

        // Assert
        var method = classBuilder.Methods.First();
        method.Statements
            .Should().Contain(s => s.Contains("_logger.LogInformation"));
    }
}

Testing Factory Extensions

ValidationExtensionTests.cs
using Xunit;
using FluentAssertions;
using Moq;

public class ValidationExtensionTests
{
    [Fact]
    public void Extension_ThrowsWhenNoEntitiesFound()
    {
        // Arrange
        var mockApp = CreateMockApplication();
        mockApp.Setup(a => a.MetadataManager.Domain(It.IsAny<IApplication>()))
            .Returns(new EmptyDomainMetadata());

        var extension = new ValidationExtension();

        // Act
        Action act = () => extension.OnAfterMetadataLoad(mockApp.Object);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*No entities found*");
    }

    [Fact]
    public void Extension_ValidatesDuplicateNames()
    {
        // Arrange
        var mockApp = CreateMockApplication();
        var entities = new[]
        {
            CreateMockEntity("Customer"),
            CreateMockEntity("Customer") // Duplicate
        };
        
        mockApp.Setup(a => a.MetadataManager.Domain(It.IsAny<IApplication>()))
            .Returns(CreateDomainMetadata(entities));

        var extension = new ValidationExtension();

        // Act
        Action act = () => extension.OnAfterMetadataLoad(mockApp.Object);

        // Assert
        act.Should().Throw<InvalidOperationException>()
            .WithMessage("*Duplicate entity names*");
    }

    [Fact]
    public void Extension_RegistersAdditionalTemplates()
    {
        // Arrange
        var mockApp = CreateMockApplication();
        var serviceTemplates = new[]
        {
            CreateMockServiceTemplate("CustomerService"),
            CreateMockServiceTemplate("OrderService")
        };

        mockApp.Setup(a => a.FindTemplateInstances(
            It.IsAny<string>()))
            .Returns(serviceTemplates);

        var extension = new DynamicTemplateExtension();
        var registeredTemplates = new List<ITemplate>();
        
        mockApp.Setup(a => a.RegisterTemplate(It.IsAny<ITemplate>()))
            .Callback<ITemplate>(t => registeredTemplates.Add(t));

        // Act
        extension.OnAfterTemplateRegistrations(mockApp.Object);

        // Assert
        registeredTemplates.Should().HaveCount(2);
        registeredTemplates.Should().AllBeOfType<TestTemplate>();
    }
}

Integration Testing

Full Module Integration Tests

ModuleIntegrationTests.cs
using Xunit;
using FluentAssertions;
using System.IO;

public class ModuleIntegrationTests : IDisposable
{
    private readonly string _testOutputPath;

    public ModuleIntegrationTests()
    {
        _testOutputPath = Path.Combine(
            Path.GetTempPath(), 
            $"IntentTest_{Guid.NewGuid()}");
        Directory.CreateDirectory(_testOutputPath);
    }

    [Fact]
    public async Task Module_GeneratesExpectedFiles()
    {
        // Arrange
        var application = await CreateTestApplication();
        
        // Act
        await application.RunSoftwareFactory();

        // Assert
        var generatedFiles = Directory.GetFiles(
            _testOutputPath, 
            "*.cs", 
            SearchOption.AllDirectories);

        generatedFiles.Should().Contain(f => 
            f.EndsWith("Customer.cs"));
        generatedFiles.Should().Contain(f => 
            f.EndsWith("CustomerRepository.cs"));
    }

    [Fact]
    public async Task Module_GeneratesCompilableCode()
    {
        // Arrange
        var application = await CreateTestApplication();
        await application.RunSoftwareFactory();

        // Act
        var compilation = await CompileGeneratedCode(_testOutputPath);

        // Assert
        compilation.Success.Should().BeTrue();
        compilation.Errors.Should().BeEmpty();
    }

    private async Task<TestApplication> CreateTestApplication()
    {
        var app = new TestApplication(_testOutputPath);
        await app.InstallModule("MyModule");
        app.AddEntity("Customer", new[]
        {
            ("Id", "int"),
            ("Name", "string")
        });
        return app;
    }

    public void Dispose()
    {
        if (Directory.Exists(_testOutputPath))
        {
            Directory.Delete(_testOutputPath, true);
        }
    }
}

Testing with Real Metadata

Create Test Applications

Set up test Intent applications:
Tests/
├── Fixtures/
│   ├── BasicDomain/
│   │   ├── BasicDomain.application.config
│   │   └── Domain/
│   │       └── Entities.pkg
│   ├── ComplexDomain/
│   │   ├── ComplexDomain.application.config
│   │   └── Domain/
│   └── EdgeCases/
│       └── ...
└── IntegrationTests.cs

Test Against Fixtures

FixtureBasedTests.cs
public class FixtureBasedTests
{
    [Theory]
    [InlineData("BasicDomain")]
    [InlineData("ComplexDomain")]
    [InlineData("EdgeCases")]
    public async Task Module_WorksWithFixture(string fixtureName)
    {
        // Arrange
        var fixturePath = Path.Combine("Fixtures", fixtureName);
        var application = LoadApplication(fixturePath);

        // Act
        var result = await application.RunSoftwareFactory();

        // Assert
        result.Success.Should().BeTrue();
        result.Errors.Should().BeEmpty();
        
        // Verify specific outputs
        await VerifyFixtureOutput(fixtureName, result);
    }

    private async Task VerifyFixtureOutput(
        string fixtureName, 
        SoftwareFactoryResult result)
    {
        var verifySettings = new VerifySettings();
        verifySettings.UseDirectory($"Fixtures/{fixtureName}/Expected");
        
        await Verifier.Verify(result.GeneratedFiles, verifySettings);
    }
}

Testing CSharp Builder Usage

CSharpBuilderTests.cs
using Xunit;
using FluentAssertions;
using Intent.Modules.Common.CSharp.Builder;

public class CSharpBuilderTests
{
    [Fact]
    public void Builder_CreatesClassCorrectly()
    {
        // Arrange & Act
        var file = new CSharpFile("MyNamespace", "MyLocation")
            .AddClass("Customer", @class =>
            {
                @class.AddProperty("int", "Id");
                @class.AddProperty("string", "Name");
                @class.AddMethod("void", "Process");
            });

        var output = file.ToString();

        // Assert
        output.Should().Contain("namespace MyNamespace");
        output.Should().Contain("class Customer");
        output.Should().Contain("public int Id { get; set; }");
        output.Should().Contain("public string Name { get; set; }");
        output.Should().Contain("public void Process()");
    }

    [Fact]
    public async Task Builder_GeneratesExpectedOutput()
    {
        // Arrange
        var file = CreateComplexClass();

        // Act
        var output = file.ToString();

        // Assert
        await Verifier.Verify(output);
    }

    private CSharpFile CreateComplexClass()
    {
        var file = new CSharpFile("TestNamespace", "TestLocation")
            .AddUsing("System")
            .AddUsing("System.Collections.Generic");

        file.AddClass("ComplexClass", @class =>
        {
            @class.AddAttribute("[Generated]");
            @class.ImplementsInterface("IDisposable");

            @class.AddField("ILogger", "_logger", f => f.PrivateReadOnly());

            @class.AddConstructor(ctor =>
            {
                ctor.AddParameter("ILogger", "logger");
                ctor.AddStatement("_logger = logger;");
            });

            @class.AddMethod("void", "Dispose", m =>
            {
                m.AddStatement("_logger.LogInformation(\\\"Disposing\\\");");
            });
        });

        return file;
    }
}

Manual Testing

Test Application Setup

1
Create Test Application
2
intent new TestApp --template basic
cd TestApp
3
Install Your Module
4
# Add local repository
intent repository add local file:///path/to/your/modules

# Install module
intent install-module MyModule

# Run Software Factory
intent run-software-factory
5
Create Test Metadata
6
In Intent Architect:
7
  • Open domain designer
  • Create test entities with various configurations
  • Apply different stereotypes
  • Create associations
  • Run Software Factory
  • 8
    Verify Output
    9
    Check generated files:
    10
  • Code compiles without errors
  • Formatting is correct
  • Logic is sound
  • Edge cases handled
  • Continuous Testing

    GitHub Actions

    .github/workflows/test.yml
    name: Module Tests
    
    on: [push, pull_request]
    
    jobs:
      test:
        runs-on: ubuntu-latest
        
        steps:
        - uses: actions/checkout@v3
        
        - name: Setup .NET
          uses: actions/setup-dotnet@v3
          with:
            dotnet-version: '8.0.x'
        
        - name: Restore dependencies
          run: dotnet restore
        
        - name: Build
          run: dotnet build --no-restore
        
        - name: Test
          run: dotnet test --no-build --verbosity normal
        
        - name: Upload test results
          if: always()
          uses: actions/upload-artifact@v3
          with:
            name: test-results
            path: '**/TestResults/*.trx'
    

    Test Organization

    MyModule.Tests/
    ├── Unit/
    │   ├── Templates/
    │   │   ├── EntityTemplateTests.cs
    │   │   ├── RepositoryTemplateTests.cs
    │   │   └── ServiceTemplateTests.cs
    │   ├── Decorators/
    │   │   └── LoggingDecoratorTests.cs
    │   ├── FactoryExtensions/
    │   │   └── ValidationExtensionTests.cs
    │   └── Helpers/
    │       └── EntityHelperTests.cs
    ├── Integration/
    │   ├── ModuleIntegrationTests.cs
    │   └── TemplateOutputTests.cs
    ├── Snapshots/
    │   ├── EntityTemplateTests.*.verified.txt
    │   └── ...
    ├── Fixtures/
    │   ├── BasicDomain/
    │   ├── ComplexDomain/
    │   └── EdgeCases/
    └── TestHelpers/
        ├── MockBuilders.cs
        └── TestApplicationFactory.cs
    

    Best Practices

    Write Tests First

    // 1. Write failing test
    [Fact]
    public void EntityTemplate_IncludesConstructor()
    {
        var template = CreateTemplate();
        var output = template.TransformText();
        output.Should().Contain("public Customer(");
    }
    
    // 2. Implement feature
    // 3. Test passes
    

    Test Edge Cases

    [Theory]
    [InlineData(null)]
    [InlineData("")]
    [InlineData("   ")]
    public void Template_HandlesInvalidInput(string input)
    {
        // Test handles null, empty, whitespace
    }
    
    [Fact]
    public void Template_HandlesEmptyCollection()
    {
        // Test with no properties
    }
    
    [Fact]
    public void Template_HandlesSpecialCharacters()
    {
        // Test with special chars in names
    }
    

    Use Descriptive Names

    // Good
    [Fact]
    public void EntityTemplate_WhenEntityHasNoProperties_GeneratesEmptyClass()
    
    // Bad
    [Fact]
    public void Test1()
    

    Keep Tests Independent

    // Each test creates its own data
    public class EntityTemplateTests
    {
        [Fact]
        public void Test1()
        {
            var model = CreateTestModel(); // New instance
            // ...
        }
    
        [Fact]
        public void Test2()
        {
            var model = CreateTestModel(); // New instance
            // ...
        }
    }
    

    Troubleshooting Tests

    • Run dotnet test locally to update snapshots
    • Ensure line endings are consistent (Git settings)
    • Check for environment-specific paths
    • Use UseDirectory() and UseFileName() for organization
    • Verify all dependencies are mocked
    • Check Setup() calls match actual usage
    • Use It.IsAny<T>() for flexible matching
    • Enable Moq strict mode to catch issues
    • Check path separators (use Path.Combine)
    • Ensure test files are included in build
    • Verify .NET SDK version matches
    • Check for timezone or culture dependencies

    Next Steps

    Creating Modules

    Complete guide to module creation

    Creating Templates

    Template development guide

    Creating Decorators

    Decorator development guide

    Factory Extensions

    Factory extension development

    Build docs developers (and LLMs) love