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();
}