Table of Contents

Advanced Testing

Advanced testing scenarios for complex modules.

Testing Modules with Multiple Outputs

Some modules send messages to different outputs based on conditions. Configure multiple outputs when setting up your test flow:

[Fact]
public async Task TestModuleWithMultipleOutputs()
{
    var module = new MyMultiOutputModule();
    var flow = module.SetupFlow(numberOfOutputs: 2);

    await flow.Start();
    
    await flow.Receive(new FlowMessage());
    
    // Get results from different outputs
    var result1 = await flow.GetNextResult(outputModuleIndex: 0);
    var result2 = await flow.GetNextResult(outputModuleIndex: 1);
    
    await flow.Stop();
}

Testing Modules with Credentials

Inject credentials into your module for testing external integrations:

[Fact]
public async Task TestModuleWithCredentials()
{
    var module = new MyModule();
    var credential = new DatabaseCredential
    {
        Id = Guid.NewGuid(),
        ConnectionString = "test-connection-string"
    };
    
    var flow = module.SetupFlow()
        .WithCredential(credential);

    await flow.Start();
    
    var message = new FlowMessage();
    message.Set("query", "SELECT * FROM users");
    await flow.Receive(message);
    
    var result = await flow.GetNextResult();
    Assert.True(result.IsSuccess);
    
    await flow.Stop();
}

Loading Credentials from File

var flow = module.SetupFlow()
    .WithCredential<MyCredential>("test-credentials.json");

Testing Modules with Resources

Inject resources like configuration files or templates:

[Fact]
public async Task TestModuleWithResources()
{
    var module = new MyModule();
    var resource = new FileResource
    {
        Id = Guid.NewGuid(),
        FileName = "test-file.txt",
        Data = Encoding.UTF8.GetBytes("test content")
    };
    
    var flow = module.SetupFlow()
        .WithResource(resource);

    await flow.Start();
    
    var message = new FlowMessage();
    await flow.Receive(message);
    
    var result = await flow.GetNextResult();
    Assert.Contains("test content", result.Get<string>("output"));
    
    await flow.Stop();
}

Batch Message Testing

Test modules that process multiple messages:

[Fact]
public async Task TestBatchProcessing()
{
    var module = new RandomNumberModule();
    module.Settings.TargetMin = 1;
    module.Settings.TargetMax = 1000;
    module.Settings.TargetName = "randomId";
    
    var flow = module.SetupFlow(timeout: 30000);

    await flow.Start();

    // Send multiple messages
    for (int i = 0; i < 10; i++)
    {
        var message = new FlowMessage();
        message.Set("data.index", i);
        await flow.Receive(message);
    }

    // Collect all results
    var results = await flow.GetNextResults(10);
    Assert.Equal(10, results.Count);
    
    // Verify each result has both original and new data
    for (int i = 0; i < 10; i++)
    {
        Assert.Equal(i, results[i].Get<int>("data.index"));
        Assert.True(results[i].Has("randomId"));
    }

    await flow.Stop();
}

Testing Specific Result Position

Test that a specific message in a sequence is processed correctly:

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

    await flow.Start();

    // Send multiple messages with sequence numbers
    for (int i = 0; i < 5; i++)
    {
        var message = new FlowMessage();
        message.Set("sequence", i);
        await flow.Receive(message);
    }

    // Get the 3rd result (0-indexed)
    var thirdResult = await flow.GetNthResult(2);
    Assert.Equal(2, thirdResult.Get<int>("sequence"));
    Assert.True(thirdResult.Has("value"));
    
    await flow.Stop();
}

Complete Test Class Example

A full test class showing best practices:

using Xunit;
using Crosser.EdgeNode.Modules.Testing;

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

    [Fact]
    public async Task GeneratesRandomNumberInRange()
    {
        await this.flow.Start();

        var message = new FlowMessage();
        await this.flow.Receive(message);

        var result = await this.flow.GetNextResult();
        Assert.True(result.Has("randomValue"));
        var value = result.Get<int>("randomValue");
        Assert.InRange(value, 0, 99);

        await this.flow.Stop();
    }

    [Fact]
    public async Task HandlesMultipleMessages()
    {
        await this.flow.Start();

        for (int i = 0; i < 3; i++)
        {
            var message = new FlowMessage();
            message.Set("data.sequence", i);
            await this.flow.Receive(message);
        }

        var results = await this.flow.GetNextResults(3);
        Assert.Equal(3, results.Count);
        
        // Verify each result has both original and generated data
        for (int i = 0; i < 3; i++)
        {
            Assert.Equal(i, results[i].Get<int>("data.sequence"));
            Assert.True(results[i].Has("randomValue"));
        }

        await this.flow.Stop();
    }

    [Fact]
    public async Task UsesConfiguredPropertyName()
    {
        // Change the target property name
        this.module.Settings.TargetName = "sensor.temperature";
        
        var testFlow = this.module.SetupFlow();

        await testFlow.Start();
        
        var message = new FlowMessage();
        await testFlow.Receive(message);

        var result = await testFlow.GetNextResult();
        Assert.True(result.Has("sensor.temperature"));
        Assert.False(result.Has("randomValue"));

        await testFlow.Stop();
    }

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

>> Best Practices