Skip to content

Conversation

@lillo42
Copy link
Contributor

@lillo42 lillo42 commented Jan 29, 2026

Overview

Extends the \Paramore.Brighter.Test.Generator` tool to automatically generate standardized tests for messaging gateway implementations (RabbitMQ, AWS SNS/SQS, Azure Service Bus, etc.), reducing code duplication and
ensuring consistent test coverage across all gateway implementations.

What's Changed

Configuration System

  • Added MessagingGatewayConfiguration class with support for:
    • Test class naming and categorization
    • Custom message factories and assertion helpers
    • Gateway-specific publication/subscription configuration
  • Async operation timing controls
  • Extended TestConfiguration with MessagingGateway and MessagingGatewaies properties
  • Added comprehensive XML documentation for all configuration classes

Generator Implementation

  • New MessagingGatewayGenerator supporting both:
    • Reactor pattern (synchronous operations)
    • Proactor pattern (asynchronous operations)
  • Follows the same architecture as the existing OutboxGenerator for consistency
  • Generates provider interfaces and test implementations from Liquid templates

Message Factory & Assertions

  • Refactored message creation to use MessageConfiguration for complete control over test message properties
  • New IAmAMessageFactory interface and DefaultMessageFactory implementation
  • New IAmAMessageAssertion interface and DefaultMessageAssertion implementation
  • Factories now track created messages via CreatedMessages property

Templates

  • MessagingGateway/Proactor/ - Async test templates
  • MessagingGateway/Reactor/ - Sync test templates
  • Provider interfaces and assertion implementations

Documentation

  • Added ADR 0037 documenting the design decisions
  • Added comprehensive XML documentation to all configuration classes and templates

Benefits

  • ✅ Eliminates code duplication across gateway implementations
  • ✅ Ensures consistent test coverage for all messaging gateways
  • ✅ Reduces maintenance burden when test patterns change
  • ✅ Simplifies adding new gateway implementations with complete test coverage

@lillo42 lillo42 marked this pull request as draft January 29, 2026 15:56
codescene-delta-analysis[bot]

This comment was marked as outdated.

@lillo42 lillo42 added 2 - In Progress .NET Pull requests that update .net code labels Jan 29, 2026
@claude
Copy link

claude bot commented Jan 29, 2026

Pull Request Review: Add MessageGateway Test Generator

Summary

This PR introduces a test generator for messaging gateway implementations, extending the existing test generation infrastructure to reduce code duplication across different messaging providers (RabbitMQ, AWS SNS/SQS, Azure Service Bus, etc.). The implementation follows the established pattern from the Outbox generator and includes comprehensive documentation via ADR 0037.

Positive Aspects ✅

Architecture & Design

  • Excellent ADR documentation: ADR 0037 is comprehensive, well-structured, and clearly documents the context, decision, and consequences
  • Consistent with existing patterns: Follows the same approach as OutboxGenerator, maintaining consistency in the codebase
  • Good separation of concerns: Configuration, generation logic, and templates are properly separated
  • Extensible design: Supports both single and multiple gateway configurations via MessagingGateway and MessagingGatewaies properties

Code Quality

  • Proper licensing: All new files include the MIT license header as required
  • Auto-generated markers: Generated files are properly marked with <auto-generated> comments
  • XML documentation: Classes and public members include comprehensive XML documentation
  • Type safety: Proper use of nullable reference types throughout

Issues & Concerns 🔴

1. Critical: Null Reference Issues in DefaultMessageFactory.cs

Location: tests/Paramore.Brighter.RMQ.Async.Tests/DefaultMessageFactory.cs:290-321 and template

The Create method has a major bug - it accepts MessageConfiguration? (nullable) but immediately dereferences it without null checking:

public Message Create(MessageConfiguration? configuration = null)
{
    var messageHeader = new MessageHeader(
        messageId: configuration.MessageId,  // ⚠️ NullReferenceException if configuration is null
        topic: configuration.Topic,
        // ... all other properties accessed without null check

Fix: Either:

  1. Make parameter non-nullable: MessageConfiguration configuration with a default instance, OR
  2. Add null coalescing: configuration ??= new MessageConfiguration(); at the start

Severity: HIGH - This will cause runtime exceptions

2. Code Style Violation: Inconsistent Spacing

Location: tools/Paramore.Brighter.Test.Generator/Templates/IAmAMessageFactory.cs.liquid:1526-1644

The MessageConfiguration class has inconsistent indentation. Properties use 2-space indents instead of the standard 4-space indents used elsewhere:

public class MessageConfiguration
{
    public Dictionary<string, object> Bag { get; set; } = new() { ... };

  public Baggage Baggage { get; set; } = new();  // ⚠️ 2 spaces instead of 4
  
  public ContentType ContentType { get; set; } = ...  // ⚠️ Inconsistent

Fix: Use consistent 4-space indentation for all properties

Severity: MEDIUM - Code style violation

3. Typo in Configuration Property Name

Location: tools/Paramore.Brighter.Test.Generator/Configuration/TestConfiguration.cs:877

Property name has a typo: MessagingGatewaies should be MessagingGateways (standard English plural)

public Dictionary<string, MessagingGatewayConfiguration>? MessagingGatewaies { get; set; }

Impact: This affects the public API of the configuration and should be corrected before release

Severity: MEDIUM - Breaking change required

4. Hard-coded Delay Values

Location: tests/Paramore.Brighter.RMQ.Async.Tests/MessagingGateway/Generated/Proactor/When_a_message_consumer_reads_multiple_messages_should_receive_all_messages.cs:590,607

The generated test has hard-coded delays:

  • await Task.Delay(5000); (line 590)
  • await Task.Delay(5000); (line 607)

While the configuration has DelayBetweenReceiveMessageInMilliseconds: 5000, these values should be configurable at the test configuration level, not hard-coded in the generated test. The template correctly uses the configuration value for the second delay (line 607), but the first delay at line 590 is hard-coded.

Severity: LOW - Test maintainability issue

5. Missing XML Documentation Comment

Location: tools/Paramore.Brighter.Test.Generator/Configuration/MessageConfiguration.cs (if it exists)

The MessageConfiguration class in the template lacks a class-level XML documentation comment. All public classes should have XML documentation per the project's documentation standards.

Severity: LOW - Documentation completeness

6. Commented-out Code in Generator

Location: tools/Paramore.Brighter.Test.Generator/Generators/MessagingGatewayGenerator.cs:941-947

// await GenerateAsync(
//     configuration,
//     Path.Combine("MessagingGateway", prefix, "Generated", "Reactor"),
//     Path.Combine("MessagingGateway", "Reactor"),
//     configuration.MessagingGateway,
//     filename => SkipTest(configuration.MessagingGateway, filename)
// );

Questions:

  • Is Reactor (synchronous) support intentionally deferred?
  • Should this commented code be removed or implemented?
  • If deferred, should this be documented in the ADR?

Severity: LOW - Code cleanliness

7. Empty Skip Test Implementation

Location: tools/Paramore.Brighter.Test.Generator/Generators/MessagingGatewayGenerator.cs:1001-1004

private static bool SkipTest(MessagingGatewayConfiguration configuration, string fileName)
{
    return false;
}

This always returns false, unlike OutboxGenerator.SkipTest which has logic to skip transaction tests. Consider:

  • Is this intentional or a placeholder?
  • Should there be logic to skip certain tests based on gateway capabilities?

Severity: LOW - Potential missing functionality

Test Coverage Concerns ⚠️

Missing Test Scenarios

The PR generates only ONE test scenario: When_a_message_consumer_reads_multiple_messages_should_receive_all_messages. The ADR mentions "common scenarios" but only one is implemented.

Questions:

  1. Are additional test scenarios planned (error handling, timeouts, connection failures, etc.)?
  2. Is this intentional for the initial implementation?
  3. Should the ADR clarify that only one scenario is currently implemented?

Recommendation: Consider adding at least:

  • Test for single message send/receive
  • Test for message acknowledgment/rejection
  • Test for error handling scenarios

Security Considerations 🔒

No security issues identified. The generated tests use test doubles and don't expose credentials.

Performance Considerations ⚡

  • 5-second delays in tests: The generated test has multiple 5-second delays which will slow down test execution. Consider if these can be reduced or made configurable per test environment.
  • Test parallelization: Verify that generated tests can run in parallel without conflicts (unique routing keys/channels per test)

Recommendations 📋

Must Fix Before Merge (Breaking Issues)

  1. ✅ Fix null reference exception in DefaultMessageFactory.Create()
  2. ✅ Fix typo: MessagingGatewaiesMessagingGateways

Should Fix Before Merge (Quality Issues)

  1. ✅ Fix inconsistent indentation in MessageConfiguration
  2. ✅ Remove or document commented-out Reactor generation code
  3. ✅ Add XML documentation for MessageConfiguration class
  4. ✅ Make first delay in test template configurable (not hard-coded)

Consider for Future PRs

  1. Add more test scenarios beyond multi-message read
  2. Consider reducing test delay times for faster test execution
  3. Implement skip logic for gateway-specific capabilities (if needed)
  4. Add Reactor (sync) pattern support if planned

Testing Recommendations 🧪

Before merging, verify:

  1. Run the generator against RMQ configuration and ensure tests compile
  2. Run the generated tests to ensure they pass
  3. Test with null MessageConfiguration parameter to verify the null handling fix
  4. Verify generated code follows project conventions (run code formatter/linter)

Conclusion

This is a well-architected PR that follows established patterns and includes excellent documentation. The core design is solid, but there are several critical bugs that must be fixed before merge, particularly the null reference issue in DefaultMessageFactory. Once the must-fix items are addressed, this will be a valuable addition to the test infrastructure.

Overall Assessment: Approve with required changes


🤖 Generated with Claude Code

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No application code in the PR — skipped Code Health checks.

See analysis details in CodeScene

Quality Gate Profile: Clean Code Collective
Want more control? Customize Code Health rules or catch issues early with our IDE extension and CLI tool.

@claude
Copy link

claude bot commented Jan 29, 2026

Pull Request Review: Add MessageGateway Test Generator

Summary

This PR successfully extends the test generator tool to support messaging gateway tests, following the established pattern from the Outbox generator. The implementation is well-architected, consistent with the existing codebase, and includes comprehensive documentation via ADR 0037.

✅ Strengths

Architecture & Design

  • Excellent consistency: Follows the same pattern as OutboxGenerator, making it easy for developers familiar with that pattern to understand
  • Well-documented: ADR 0037 provides thorough context, rationale, and consequences
  • Flexible configuration: Supports both single and multiple gateway configurations
  • Good separation of concerns: Generator logic, templates, and provider implementations are cleanly separated

Code Quality

  • Comprehensive XML documentation: All public APIs are well-documented
  • Proper licensing: MIT license headers present in all files
  • Auto-generated markers: Generated files clearly marked with <auto-generated> comments
  • Proper async patterns: Correct use of Task, async/await, and CancellationToken

Testing Infrastructure

  • Message configuration flexibility: MessageConfiguration class provides fine-grained control over test message properties
  • Assertion abstraction: IAmAMessageAssertion allows custom validation logic
  • Factory tracking: CreatedMessages property enables test verification

🔍 Issues & Concerns

1. Copy-paste error in log message (Minor)

Location: tools/Paramore.Brighter.Test.Generator/Generators/MessagingGatewayGenerator.cs:70

logger.LogInformation("Generating outbox test for {OutboxName}", key);

Should be:

logger.LogInformation("Generating messaging gateway test for {GatewayName}", key);

2. Commented-out code (Minor)

Location: MessagingGatewayGenerator.cs:50-56

Reactor pattern generation is commented out for single gateway configuration but enabled for multiple gateways (lines 87-93). This inconsistency suggests:

  • Either Reactor support is incomplete and should be removed from the multiple gateway path too
  • Or it should be uncommented for single gateway configuration

Recommendation: Remove commented code or add a TODO comment explaining why Reactor is disabled.

3. Typo in property name (Medium)

Location: tools/Paramore.Brighter.Test.Generator/Configuration/TestConfiguration.cs:48

public Dictionary<string, MessagingGatewayConfiguration>? MessagingGateways { get; set; }

Property is named MessagingGateways (with 's') but the ADR references it as MessagingGatewaies (line 41 of the ADR). While the code is correct, the ADR documentation has a typo.

4. Potential null reference issues (Low)

Location: Generated test template line 698

_subscription = _messageGatewayProvider.CreateSubscription(_publication.Topic!, ...)

Using null-forgiving operator (!) on _publication.Topic assumes it's always non-null. While this is likely safe given the factory method, consider adding a null check or assertion for robustness.

5. Hardcoded delay values (Medium - Performance)

Location: Generated test at lines 717 and 732

await Task.Delay(5000);  // Hardcoded 5-second delay

The test hardcodes a 5-second delay even though the configuration specifies DelayBetweenReceiveMessageInMilliseconds: 5000. If a developer changes the config to 1000ms (as shown in the ADR example on line 144), the hardcoded delays won't update.

Recommendation: Use the configuration value from _subscription or make it a template parameter.

6. Empty SkipTest implementation (Low)

Location: MessagingGatewayGenerator.cs:110-113

private static bool SkipTest(MessagingGatewayConfiguration configuration, string fileName)
{
    return false;
}

This stub exists in OutboxGenerator to skip transaction tests when not supported. For messaging gateways, consider:

  • Are there test scenarios that some gateways don't support?
  • If not, document why this always returns false (future extensibility)

7. Missing validation (Low)

Location: MessagingGatewayConfiguration.cs

The configuration properties Publication and Subscription are marked as string.Empty defaults but are required. Consider:

  • Adding validation in the generator to fail fast with clear error messages
  • Making them nullable and checking for null explicitly

💡 Suggestions for Improvement

1. Test Coverage

The PR only includes one generated test: When_a_message_consumer_reads_multiple_messages_should_receive_all_messages. Consider:

  • Are there more test templates planned?
  • Should there be tests for error scenarios (connection failures, malformed messages)?
  • Single message send/receive tests?

2. Partition Key Handling

Location: Line 707-710 of generated test

_messageFactory.Create(new MessageConfiguration{Topic = _publication.Topic, PartitionKey = null}),

The partition key is explicitly set to null. This seems intentional but isn't documented. Consider:

  • Adding a comment explaining why
  • Or making it configurable per gateway

3. Resource Cleanup Ordering

Location: RmqMessageGatewayProvider.CleanUpAsync:24-35

Channel is disposed synchronously (channel.Dispose()) after async purge, while producer uses DisposeAsync(). Consider making channel disposal consistent by implementing IAsyncDisposable on channels or documenting why synchronous disposal is appropriate.

🔒 Security Considerations

No security concerns identified. The generated tests use localhost RabbitMQ connections with guest credentials, which is appropriate for test environments.

⚡ Performance Considerations

Test execution time: The hardcoded 5-second delays mean each 4-message test takes ~20+ seconds. For large test suites, this could be significant. Consider:

  • Making delays configurable per environment (shorter for CI)
  • Using polling with timeout instead of fixed delays
  • Documenting expected test duration

📋 Documentation & Maintainability

Excellent documentation overall. The ADR is thorough and includes:

  • Clear context and rationale
  • Consequences (positive and negative)
  • Implementation examples
  • Migration path

Minor improvement: Add a section in the ADR about how to add new test scenarios in the future.

🎯 Recommendations

Must Fix (Before Merge)

  1. Fix the log message copy-paste error (line 70 of MessagingGatewayGenerator.cs)

Should Fix (Before or Shortly After Merge)

  1. Remove or document commented Reactor code
  2. Fix typo in ADR (MessagingGatewaiesMessagingGateways)
  3. Use configuration value instead of hardcoded delays in templates

Nice to Have (Future Work)

  1. Add validation for required configuration properties
  2. Document or expand test scenario coverage
  3. Consider performance impact of delays and optimize if needed

✅ Final Assessment

Recommendation: APPROVE with minor changes

This is a high-quality PR that:

  • Follows established architectural patterns
  • Includes excellent documentation
  • Provides real value by reducing test duplication
  • Has comprehensive XML documentation
  • Includes working example (RabbitMQ)

The issues identified are mostly minor and can be addressed in follow-up commits or as part of this PR. The core architecture is sound and ready for use.

Estimated risk level: Low
Test coverage impact: Positive (enables consistent coverage across gateways)
Maintenance burden: Low (follows established patterns)


Great work @lillo42! This is a valuable addition to the test infrastructure. 🎉

@iancooper
Copy link
Member

I just want to say how excited I am for this.

Copy link
Member

@iancooper iancooper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can think of a few other points, but let's start with these.

- Generates tests from Liquid templates
- Follows the same pattern as `OutboxGenerator` for consistency

### 3. Updated Message Factories and Assertions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I note that I have had more success when running the Dispatcher using a handler that takes the FakeTimeProvider to advance the time, rather than using Task Delay, when timing is being used to trigger a behaviour. It's possible a few tests could benefit from this


### Negative

- **Learning Curve**: Developers need to understand the generator and template system
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where to start when you want to add a new feature to our MessagingGateways as well. Much harder to iterate on in-memory and then roll out. We need to document the recommended loop

_messageAssertion = new DefaultMessageAssertion();
}

public Task InitializeAsync()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems a little bit unnecessary. What forces this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the IAsyncLifeTime

if (subscription.MakeChannels == OnMissingChannel.Create)
{
// Ensuring that the queue exists before return the channel
await channel.ReceiveAsync(TimeSpan.FromMilliseconds(100), cancellationToken);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems problematic to have to use Receive to force the call to EnsureChannel. I wonder if we need an Init method for a channel that also calls EnsureChannel. (Receive should still call EnsureChannel, but we would have an explicit Init too)

);
}

public ChannelName GetOrCreateChannelName([CallerMemberName] string? testName = null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this, over a property set via the constructor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'll set the test name at compilation time, it's an option I prefer via this attribute because I don't need to force those who implement it to have a constructor

/// <summary>
/// Default implementation of <see cref="IAmAMessageFactory"/> that creates messages with randomly generated test data.
/// </summary>
public class DefaultMessageFactory : IAmAMessageFactory
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could use the Builder pattern here, which would allow you to have default values and then override properties: https://blog.ploeh.dk/2017/08/21/generalised-test-data-builder/

/// <summary>
/// Defines a contract for asserting equality between two Message instances.
/// </summary>
public interface IAmAMessageAssertion
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this an interface? Not sure I understand the thinking

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to assert the received message against the provided message. In some message gateways like RabbitMQ, the MessageId will be different on message Requeue, so I want to use this interface to cover this cases

/// <summary>
/// Provides configuration for creating test messages with customizable header values and body content.
/// </summary>
public class MessageConfiguration
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this. A message has a wide range of defaults after all

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll move to a build, as suggested in the previous comment

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 - In Progress .NET Pull requests that update .net code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants