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