Skip to main content

Overview

The Intent.UnitTesting module adds infrastructure and support for unit testing your application using xUnit. It generates test project scaffolding, test stubs for application logic, and integrates with popular mocking frameworks.
This module currently generates unit test stubs only — actual test implementations must be written manually. AI-powered test generation is available through the Intent.AI.UnitTests module.

What Gets Generated

Test Projects

  • xUnit test projects with proper structure and dependencies
  • Test infrastructure including base classes and utilities
  • NuGet package references for xUnit and mocking frameworks

Test Stubs

The module generates stub test classes for:
  • Command Handlers (for CQRS-based services)
  • Query Handlers (for CQRS-based services)
  • Service Operations (for traditional services)
  • Domain Event Handlers
  • Integration Event Handlers
Example generated test stub:
Application.Tests/CommandHandlers/CreateCustomerCommandHandlerTests.cs
using Moq;
using Xunit;

[IntentManaged(Mode.Merge)]
public class CreateCustomerCommandHandlerTests
{
    private readonly Mock<ICustomerRepository> _customerRepositoryMock;
    private readonly CreateCustomerCommandHandler _handler;

    [IntentManaged(Mode.Ignore)]
    public CreateCustomerCommandHandlerTests()
    {
        _customerRepositoryMock = new Mock<ICustomerRepository>();
        _handler = new CreateCustomerCommandHandler(_customerRepositoryMock.Object);
    }

    [Fact]
    [IntentManaged(Mode.Ignore)]
    public async Task Handle_ValidCommand_CreatesCustomer()
    {
        // Arrange
        var command = new CreateCustomerCommand
        {
            Name = "Test Customer",
            Email = "[email protected]"
        };

        // Act
        var result = await _handler.Handle(command, CancellationToken.None);

        // Assert
        Assert.NotNull(result);
        _customerRepositoryMock.Verify(x => x.Add(It.IsAny<Customer>()), Times.Once);
    }
}

Installation

Prerequisites

  • A .NET application created in Intent Architect
  • Application logic (Commands, Queries, or Services) to test

Installation Steps

1

Install the module

In Intent Architect, right-click on your application and select Manage Modules. Search for Intent.UnitTesting and install it.
2

Configure generation mode

Navigate to Application Settings and configure whether test generation should be opt-in or automatic (see Generation Mode).
3

Choose mocking framework

Select your preferred mocking framework in Application Settings (Moq or NSubstitute).
4

Run the Software Factory

Execute the Software Factory to generate the test project and infrastructure.

Configuration

Generation Mode

The module defaults to opt-in mode for test generation. Tests are only generated for elements explicitly marked with the Unit Test stereotype. Change to automatic generation:
  1. Open Application Settings
  2. Navigate to UnitTesting section
  3. Set Generation Mode to Generate for all
This will automatically generate test stubs for all:
  • Commands
  • Queries
  • Services / Operations
Unit Test Generation Mode

Mocking Framework

Choose between supported mocking libraries:
  • Moq (default) - Popular, fluent API
  • NSubstitute - Simpler syntax, natural C# syntax
Configure in Application Settings:
  1. Navigate to Application Settings
  2. Find UnitTesting section
  3. Select Mocking Framework

Usage Examples

Opt-In Test Generation

To generate tests for specific elements:
1

Apply Unit Test stereotype

In the Services Designer, right-click on a Command, Query, Service, or Operation and select Add StereotypeUnit Test.Unit Test Stereotype
2

Run the Software Factory

Execute the Software Factory to generate the test stub.
3

Implement test logic

Open the generated test file and implement your test assertions.

Using Moq (Default)

Tests/GetOrderQueryHandlerTests.cs
using Moq;
using Xunit;

public class GetOrderQueryHandlerTests
{
    private readonly Mock<IOrderRepository> _repositoryMock;
    private readonly GetOrderQueryHandler _handler;

    public GetOrderQueryHandlerTests()
    {
        _repositoryMock = new Mock<IOrderRepository>();
        _handler = new GetOrderQueryHandler(_repositoryMock.Object);
    }

    [Fact]
    public async Task Handle_ExistingOrder_ReturnsOrderDto()
    {
        // Arrange
        var orderId = Guid.NewGuid();
        var order = new Order { Id = orderId, Total = 100m };
        _repositoryMock.Setup(x => x.FindByIdAsync(orderId, default))
            .ReturnsAsync(order);

        var query = new GetOrderQuery { Id = orderId };

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        Assert.NotNull(result);
        Assert.Equal(orderId, result.Id);
        Assert.Equal(100m, result.Total);
    }

    [Fact]
    public async Task Handle_NonExistentOrder_ThrowsNotFoundException()
    {
        // Arrange
        var orderId = Guid.NewGuid();
        _repositoryMock.Setup(x => x.FindByIdAsync(orderId, default))
            .ReturnsAsync((Order?)null);

        var query = new GetOrderQuery { Id = orderId };

        // Act & Assert
        await Assert.ThrowsAsync<NotFoundException>(
            () => _handler.Handle(query, CancellationToken.None)
        );
    }
}

Using NSubstitute

Tests/DeleteProductCommandHandlerTests.cs
using NSubstitute;
using Xunit;

public class DeleteProductCommandHandlerTests
{
    private readonly IProductRepository _repository;
    private readonly DeleteProductCommandHandler _handler;

    public DeleteProductCommandHandlerTests()
    {
        _repository = Substitute.For<IProductRepository>();
        _handler = new DeleteProductCommandHandler(_repository);
    }

    [Fact]
    public async Task Handle_ValidCommand_DeletesProduct()
    {
        // Arrange
        var productId = Guid.NewGuid();
        var product = new Product { Id = productId };
        _repository.FindByIdAsync(productId, Arg.Any<CancellationToken>())
            .Returns(product);

        var command = new DeleteProductCommand { Id = productId };

        // Act
        await _handler.Handle(command, CancellationToken.None);

        // Assert
        await _repository.Received(1).Remove(product);
    }
}

Best Practices

Use Descriptive Test Names

Follow the pattern MethodName_Scenario_ExpectedBehavior for clear test intent.

Arrange-Act-Assert

Structure tests with clear sections: setup (Arrange), execution (Act), and verification (Assert).

Test Edge Cases

Don’t just test the happy path — include null checks, boundary conditions, and error scenarios.

Keep Tests Isolated

Each test should be independent and not rely on the execution order or state from other tests.

Testing Command Handlers

// Test successful creation
[Fact]
public async Task Handle_ValidData_CreatesEntity()
{
    // Arrange
    var command = CreateValidCommand();
    
    // Act
    var result = await _handler.Handle(command, CancellationToken.None);
    
    // Assert
    Assert.NotNull(result);
    _repositoryMock.Verify(x => x.Add(It.IsAny<Entity>()), Times.Once);
}

// Test validation failure
[Fact]
public async Task Handle_InvalidData_ThrowsValidationException()
{
    // Arrange
    var command = new CreateCommand { RequiredField = null };
    
    // Act & Assert
    await Assert.ThrowsAsync<ValidationException>(
        () => _handler.Handle(command, CancellationToken.None)
    );
}

Testing Query Handlers

// Test successful retrieval
[Fact]
public async Task Handle_ExistingEntity_ReturnsDto()
{
    // Arrange
    var entity = CreateTestEntity();
    _repositoryMock.Setup(x => x.FindByIdAsync(entity.Id, default))
        .ReturnsAsync(entity);
    
    // Act
    var result = await _handler.Handle(new GetQuery { Id = entity.Id }, default);
    
    // Assert
    Assert.NotNull(result);
    Assert.Equal(entity.Id, result.Id);
}

// Test not found scenario
[Fact]
public async Task Handle_NonExistentEntity_ThrowsNotFoundException()
{
    // Arrange
    _repositoryMock.Setup(x => x.FindByIdAsync(It.IsAny<Guid>(), default))
        .ReturnsAsync((Entity?)null);
    
    // Act & Assert
    await Assert.ThrowsAsync<NotFoundException>(
        () => _handler.Handle(new GetQuery { Id = Guid.NewGuid() }, default)
    );
}

Integration with Other Modules

AI Unit Tests

Use AI to automatically implement test logic instead of writing manually.

Integration Testing

Combine with Intent.AspNetCore.IntegrationTesting for full-stack API tests.

FluentValidation

Tests automatically include validators when using Intent.Application.FluentValidation.

Azure Pipelines

Generated tests run automatically in CI/CD pipelines.

Next Steps

AI Unit Tests

Generate complete test implementations with AI

Integration Testing

Test your APIs end-to-end with integration tests

CI/CD Setup

Automate test execution in Azure Pipelines

Build docs developers (and LLMs) love