Skip to main content

AwesomeAssertions Integration

While standard Assert statements work perfectly well, assertion libraries can make tests more readable and expressive. NPipeline.Extensions.Testing.AwesomeAssertions is a small extension that integrates with the AwesomeAssertions library, providing a fluent, human-readable syntax for validating the output of your pipelines.

Installation

To use this integration, you need to add both the AwesomeAssertions and the NPipeline extension package to your test project:

dotnet add package AwesomeAssertions
dotnet add package NPipeline.Extensions.Testing.AwesomeAssertions

Pipeline Execution Result Assertions

With NPipeline.Extensions.Testing, you can use PipelineTestHarness<T> to test your pipelines with fluent assertions on the execution results:

using NPipeline.Extensions.Testing;
using NPipeline.Extensions.Testing.AwesomeAssertions;
using AwesomeAssertions;
using Xunit;

public class MyPipelineTests
{
[Fact]
public async Task Pipeline_ShouldCompleteSuccessfully()
{
// Arrange & Act
var result = await new PipelineTestHarness<MyPipeline>()
.WithParameter("input", testData)
.RunAsync();

// Assert - fluent API with AwesomeAssertions
result
.ShouldBeSuccessful()
.ShouldHaveNoErrors()
.ShouldCompleteWithin(TimeSpan.FromSeconds(5));
}

[Fact]
public async Task Pipeline_ShouldCaptureErrorsGracefully()
{
// Arrange & Act
var result = await new PipelineTestHarness<MyPipeline>()
.WithParameter("input", invalidData)
.CaptureErrors()
.RunAsync();

// Assert
result
.ShouldFail()
.ShouldHaveErrorOfType<InvalidOperationException>()
.ShouldHaveErrorCount(1);
}
}

Pipeline Result Assertions

The AwesomeAssertions integration provides these assertion extension methods:

MethodDescription
ShouldBeSuccessful()Assert pipeline executed successfully (no uncaught exceptions)
ShouldFail()Assert pipeline execution failed
ShouldHaveNoErrors()Assert no errors were captured
ShouldHaveErrorCount(int)Assert specific number of errors were captured
ShouldHaveErrorOfType<TException>()Assert at least one error of a specific type
ShouldCompleteWithin(TimeSpan)Assert execution completed within duration

All methods return the result for fluent chaining.

Sink Node Assertions

For asserting on individual sink nodes, use the existing sink extensions:

using NPipeline.Extensions.Testing;
using NPipeline.Extensions.Testing.AwesomeAssertions;
using AwesomeAssertions;

public class InMemorySinkTests
{
[Fact]
public async Task Sink_Should_Receive_Data()
{
// Arrange
var inputData = new[] { "a", "b" };
var context = new PipelineContext();
context.SetSourceData(inputData);

var sink = new InMemorySinkNode<string>();
var builder = new PipelineBuilder();
var source = builder.AddInMemorySource<string>();
var transform = builder.AddTransform<ToUpperTransform, string, string>();

builder.Connect(source, transform);
builder.Connect(transform, sink);

var pipeline = builder.Build();

// Act
await new PipelineRunner().RunAsync(pipeline, context);

// Assert
sink.ShouldContain("A");
sink.ShouldContain("B");
sink.ShouldHaveReceived(2);
}
}

Available Extension Methods

The InMemorySinkExtensions class provides these assertion methods for sink nodes:

Count Assertions

// Assert that sink has received a specific number of items
sink.ShouldHaveReceived(expectedCount);

// Equivalent to:
sink.Items.Count.Should().Be(expectedCount);

Content Assertions

// Assert that sink contains a specific item
sink.ShouldContain(expectedItem);

// Assert that sink contains an item matching a predicate
sink.ShouldContain(item => item.StartsWith("A"));

// Assert that sink does not contain a specific item
sink.ShouldNotContain(unexpectedItem);

// Assert that all items in sink satisfy a predicate
sink.ShouldOnlyContain(item => item.Length > 0);

Complete Example: Pipeline with Error Handling

using NPipeline.Extensions.Testing;
using NPipeline.Extensions.Testing.AwesomeAssertions;
using AwesomeAssertions;
using Xunit;

public class PipelineWithErrorHandlingTests
{
[Fact]
public async Task Pipeline_Should_Filter_And_Transform_Data()
{
// Arrange
var inputData = new[] { "apple", "banana", "cherry", "date" };
var context = new PipelineContext();
context.SetSourceData(inputData);

var builder = new PipelineBuilder();
var source = builder.AddInMemorySource<string>();
var filter = builder.AddTransform<FruitFilter, string, string>();
var transform = builder.AddTransform<ToUpperTransform, string, string>();
var sink = builder.AddInMemorySink<string>(context);

builder.Connect(source, filter);
builder.Connect(filter, transform);
builder.Connect(transform, sink);

var pipeline = builder.Build();

// Act
await new PipelineRunner().RunAsync(pipeline, context);

// Assert
sink.ShouldHaveReceived(2); // Only "apple" and "banana" should pass filter
sink.ShouldContain("APPLE");
sink.ShouldContain("BANANA");
sink.ShouldNotContain("CHERRY"); // "cherry" and "date" should be filtered out
sink.ShouldOnlyContain(item => item.Length >= 5); // All results should be 5+ characters
}

[Fact]
public async Task Pipeline_With_Error_Capturing_Should_Report_Errors()
{
// Arrange & Act
var result = await new PipelineTestHarness<PipelineThatCanFail>()
.WithParameter("input", problemData)
.CaptureErrors(PipelineErrorDecision.ContinueWithoutNode)
.RunAsync();

// Assert - fluent assertions make this very readable
result
.ShouldHaveErrorCount(2)
.ShouldHaveErrorOfType<ValidationException>()
.ShouldCompleteWithin(TimeSpan.FromSeconds(10));

// Can also access the sink
var sink = result.GetSink<InMemorySinkNode<ProcessedData>>();
sink.ShouldHaveReceived(expectedSuccessfulCount);
}

[Fact]
public async Task Pipeline_Should_Preserve_Custom_Error_Handlers()
{
// Arrange - create a context with custom error handler
var errorLog = new List<string>();
var customHandler = new LoggingErrorHandler(errorLog);

var context = new PipelineContext();
context.PipelineErrorHandler = customHandler;

// Act - test harness will chain custom handler with capturing handler
var result = await new PipelineTestHarness<MyPipeline>(context)
.WithParameter("input", testData)
.CaptureErrors()
.RunAsync();

// Assert - both error capturing AND custom logging happened
errorLog.Should().NotBeEmpty("custom handler should have logged");
result.Errors.Should().NotBeEmpty("errors should be captured");
}
}

Benefits of AwesomeAssertions

  • Readability: sink.ShouldHaveReceived(5) reads more like a natural sentence than Assert.Equal(5, sink.Items.Count).
  • Expressiveness: AwesomeAssertions provides a rich set of assertions for collections, objects, exceptions, and more. For example, you can assert that a collection contains a specific item, that all items match a predicate, or that the output is in a specific order.
  • Detailed Error Messages: When an assertion fails, AwesomeAssertions provides detailed output highlighting exactly what was different between the actual and expected values, which can significantly speed up debugging.
  • Fluent Chaining: You can chain multiple assertions together for complex validation scenarios.

Next Steps

  • FluentAssertions: If you prefer FluentAssertions, NPipeline also provides a similar integration for that library.
  • Testing Extensions: Return to the main testing documentation for more testing patterns and utilities.