Table of Contents

Best Practices

Tips for writing effective module tests.

1. Configure Appropriate Timeouts

Adjust timeouts based on your module's expected processing time:

var module = new RandomNumberModule();

// For fast modules (default)
var flow = module.SetupFlow(timeout: 5000);

// For slower modules
var flow = module.SetupFlow(timeout: 30000);

// For debugging (very long timeout)
var flow = module.SetupFlow(timeout: 300000);
Tip

Use longer timeouts during debugging to avoid timeout exceptions while stepping through code.

2. Test Error Conditions

Don't just test the happy path. Verify your module handles errors gracefully:

[Fact]
public async Task TestInvalidSettings()
{
    var module = new RandomNumberModule();
    module.Settings.TargetMin = 100;
    module.Settings.TargetMax = 10; // Invalid: min > max
    module.Settings.TargetName = "value";
    
    var flow = module.SetupFlow();

    // Validation should fail during Start
    var startResult = await flow.Start();
    Assert.False(startResult.IsSuccess);

    await flow.Stop();
}

3. Test Module Lifecycle

Verify your module handles initialization and shutdown correctly:

[Fact]
public async Task TestModuleLifecycle()
{
    var module = new RandomNumberModule();
    module.Settings.TargetMin = 0;
    module.Settings.TargetMax = 100;
    module.Settings.TargetName = "value";
    
    var flow = module.SetupFlow();

    // Test successful start
    var startResult = await flow.Start();
    Assert.True(startResult.IsSuccess);

    // Test processing
    await flow.Receive(new FlowMessage());
    var result = await flow.GetNextResult();
    Assert.NotNull(result);
    Assert.True(result.Has("value"));

    // Test successful stop
    var stopResult = await flow.Stop();
    Assert.True(stopResult.IsSuccess);
}

4. Use Meaningful Test Data

Create realistic test data that represents actual use cases:

[Fact]
public async Task TestRandomNumberDistribution()
{
    var module = new RandomNumberModule();
    module.Settings.TargetMin = 0;
    module.Settings.TargetMax = 10;
    module.Settings.TargetName = "value";
    
    var flow = module.SetupFlow();

    await flow.Start();

    // Generate multiple random numbers
    var message = new FlowMessage();
    for (int i = 0; i < 100; i++)
    {
        await flow.Receive(message.Clone());
    }

    var results = await flow.GetNextResults(100);
    
    // Verify all values are in range
    foreach (var result in results)
    {
        var value = result.Get<int>("value");
        Assert.InRange(value, 0, 9);
    }

    await flow.Stop();
}

5. Organize Tests with Test Classes

Use the IDisposable pattern for test cleanup:

public class RandomNumberModuleTests : IDisposable
{
    private readonly RandomNumberModule module;
    private readonly TestFlow flow;

    public RandomNumberModuleTests()
    {
        this.module = new RandomNumberModule();
        this.module.Settings.TargetMin = 0;
        this.module.Settings.TargetMax = 100;
        this.module.Settings.TargetName = "randomValue";
        this.flow = this.module.SetupFlow();
    }

    [Fact]
    public async Task GeneratesRandomNumber()
    {
        await this.flow.Start();
        
        await this.flow.Receive(new FlowMessage());
        var result = await this.flow.GetNextResult();
        
        Assert.True(result.Has("randomValue"));
        Assert.InRange(result.Get<int>("randomValue"), 0, 99);
        
        await this.flow.Stop();
    }

    public void Dispose()
    {
        this.flow?.Dispose();
    }
}

6. Test Edge Cases

Consider boundary conditions and edge cases:

[Theory]
[InlineData(int.MinValue)]
[InlineData(0)]
[InlineData(int.MaxValue)]
public async Task TestBoundaryValues(int value)
{
    var module = new MyModule();
    var flow = module.SetupFlow();

    await flow.Start();

    var message = new FlowMessage();
    message.Set("value", value);
    await flow.Receive(message);

    var result = await flow.GetNextResult();
    Assert.NotNull(result);

    await flow.Stop();
}

7. Verify Message Properties

Check that your module sets properties correctly:

[Fact]
public async Task VerifyOutputProperties()
{
    var module = new RandomNumberModule();
    module.Settings.TargetMin = 50;
    module.Settings.TargetMax = 150;
    module.Settings.TargetName = "sensor.temperature";
    
    var flow = module.SetupFlow();

    await flow.Start();

    await flow.Receive(new FlowMessage());
    var result = await flow.GetNextResult();

    // Verify the property was set with the configured name
    Assert.True(result.Has("sensor.temperature"));
    var temp = result.Get<int>("sensor.temperature");
    Assert.InRange(temp, 50, 149);

    await flow.Stop();
}

8. Test with Realistic Credentials

Use test credentials that match production structure:

[Fact]
public async Task TestWithRealisticCredentials()
{
    var module = new MyDatabaseModule();
    
    var credential = new DatabaseCredential
    {
        Id = Guid.NewGuid(),
        Host = "localhost",
        Port = 5432,
        Database = "testdb",
        Username = "testuser",
        Password = "testpass"
    };
    
    var flow = module.SetupFlow()
        .WithCredential(credential);

    await flow.Start();
    // Test database operations
    await flow.Stop();
}

9. Add Descriptive Test Names

Use clear, descriptive names that explain what the test verifies:

[Fact]
public async Task ProcessMessage_WithValidInput_ReturnsExpectedOutput() { }

[Fact]
public async Task ProcessMessage_WithMissingProperty_ReturnsError() { }

[Fact]
public async Task ProcessMessage_WithLargeDataset_CompletesWithinTimeout() { }

10. Document Complex Test Scenarios

Add comments for complex test logic:

[Fact]
public async Task TestComplexScenario()
{
    var module = new MyModule();
    var flow = module.SetupFlow();

    await flow.Start();

    // Simulate a sequence of related messages
    // that should trigger aggregation logic
    for (int i = 0; i < 5; i++)
    {
        var message = new FlowMessage();
        message.Set("batch.id", "batch-123");
        message.Set("batch.sequence", i);
        await flow.Receive(message);
    }

    // The module should aggregate after 5 messages
    var result = await flow.GetNextResult();
    Assert.Equal(5, result.Get<int>("batch.count"));

    await flow.Stop();
}

>> Troubleshooting