Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[QUERY] How to mock EventProcessorClient? #31533

Closed
denisivan0v opened this issue Sep 30, 2022 · 7 comments
Closed

[QUERY] How to mock EventProcessorClient? #31533

denisivan0v opened this issue Sep 30, 2022 · 7 comments
Assignees
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. Event Hubs needs-author-feedback Workflow: More information is needed from author to address the issue. no-recent-activity There has been no recent activity on this issue. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that

Comments

@denisivan0v
Copy link

denisivan0v commented Sep 30, 2022

Library name and version

Azure.Messaging.EventHubs.Processor 5.7.0

Query/Question

I'm building integration tests for a service (which runs as a worker) using the approach described here. The service receives events from EventHubs partitions using EventProcessorClient. The idea is to mock EventProcessorClient and override its instance in DI container when running within the test host.

So far, I was able to mock BlobClient and BlobContainerClient which EventProcessorClient uses for checkpointing and partition balancing, and also mocked some EventProcessorClient's behavior by overriding methods (e.g. ListPartitionIdsAsync) in inherited class. However, there are 2 questions I'd be very appreciated to get some input about:

  1. How to properly mock ProcessEventAsync event handler/raise the event from a mock?

  2. How to mock EventHubConnection (and underlying TransportClient/AmqpClient) with no .NET reflection usage, so it won't connect to EventHubs service?

    With this setup

    class MockEventProcessorClient : EventProcessorClient
    {
        private readonly Mock<EventHubConnection> _eventHubConnectionMock = new();
    
        public MockEventProcessorClient()
        {
            // Cannot do this since both CreateTransportConsumer and TransportConsumer are internal
            _eventHubConnectionMock.Setup(x => x.CreateTransportConsumer(...)).Returns(...);
        }
    
        // ...
    
        protected override EventHubConnection CreateConnection()
        {
           return _eventHubConnectionMock.Object;
        }
    }

    EventProcessorClient fails with the following

    System.NullReferenceException: Object reference not set to an instance of an object.
      at Azure.Messaging.EventHubs.Primitives.EventProcessor`1.<>c__DisplayClass67_0.<<CreatePartitionProcessor>g__performProcessing|1>d.MoveNext()
    --- End of stack trace from previous location ---
      at Azure.Messaging.EventHubs.Primitives.EventProcessor`1.<>c__DisplayClass67_0.<<CreatePartitionProcessor>g__performProcessing|1>d.MoveNext()
    

    since InnerClient.CreateConsumer obviously returns null.

Environment

.NET 6.0, Ubuntu Linux (mcr.microsoft.com/dotnet/runtime-deps:6.0-focal-amd64), AKS
Moq

@ghost ghost added the needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. label Sep 30, 2022
@azure-sdk azure-sdk added Client This issue points to a problem in the data-plane of the library. Event Hubs needs-team-triage Workflow: This issue needs the team to triage. labels Sep 30, 2022
@ghost ghost removed the needs-triage Workflow: This is a new issue that needs to be triaged to the appropriate team. label Sep 30, 2022
@m-redding
Copy link
Member

Hi! Thanks for your question. We just added a sample that outlines the basic approach for mocking the EventHubProcessorClient here.

To answer your questions:

  1. When you're testing the handlers you can actually just call them directly! You can move under the assumption that the handlers will be called by the Processor at the appropriate times. This should make testing your handlers more straightforward. There's an example that demonstrates how to mock the ProcessEventArgs and ProcessErrorEventArgs in the sample I linked above.

Before answering #2, is there anything specific you're looking to test using your mocked EventProcessorClient that isn't related to the documented behavior of the Processor client? Ideally you should be able to just test your handlers and assume the EventProcessorClient itself will work, since we have an extensive test suite of our own dedicated to the behavior of the client.

@m-redding m-redding added needs-author-feedback Workflow: More information is needed from author to address the issue. and removed needs-team-triage Workflow: This issue needs the team to triage. labels Sep 30, 2022
@m-redding m-redding self-assigned this Sep 30, 2022
@jsquire jsquire added customer-reported Issues that are reported by GitHub users external to the Azure organization. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that labels Sep 30, 2022
@denisivan0v
Copy link
Author

So, if I understood correctly, the recommendation is to avoid mocking EventHubProcessorClient, but instead focus on generating EH events via ProcessEventArgs and testing processing logic, right? If so, it might work with some unit testing scenarios, but also requires to expose event handlers in order to make them testable (e.g. as internal methods).
From the other hand, that also means we cannot test the end-to-end flow StartProcessingAsync -> PartitionInitializingAsync -> ProcessEventAsync/ProcessErrorAsync -> StopProcessingAsync -> PartitionClosingAsync.

That said, in my case I need to run integration test, meaning I need to start .NET host (with some DI registrations overridden) and test end-to-end flow, so I doubt testing the handlers approach could help.

is there anything specific you're looking to test using your mocked EventProcessorClient that isn't related to the documented behavior of the Processor client?

No, I don't want to rely on undocumented behavior or internals of EventProcessorClient. It's quite opposite actually - I'd be happy to replace a "real" EventProcessorClient with a version that does exactly the same but reads events from some in-memory array/storage created for a test. More specifically, it would be great to have something similar you described in another sample, but for EventProcessorClient instead of EventHubConsumerClient:

// Here we are mocking a partition context using the model factory.

PartitionContext partitionContext = EventHubsModelFactory.PartitionContext(
    fullyQualifiedNamespace: "sample-hub.servicebus.windows.net",
    eventHubName: "sample-hub",
    consumerGroup: "$Default",
    partitionId: "0");

// Here we are mocking an event data instance with broker-owned properties populated.

EventData eventData = EventHubsModelFactory.EventData(
    eventBody: new BinaryData("Sample-Event"),
    systemProperties: new Dictionary<string, object>(), //arbitrary value
    partitionKey: "sample-key",
    sequenceNumber: 1000,
    offset: 1500,
    enqueuedTime: DateTimeOffset.Parse("11:36 PM"));

// This creates a new instance of ProcessEventArgs to pass into the handler directly.

ProcessEventArgs processEventArgs = new(
    partition: partitionContext,
    data: eventData,
    updateCheckpointImplementation: async (CancellationToken ct) => await Task.CompletedTask); // arbitrary value

mockProcessorClient
    .Raise(x => x.ProcessEventAsync += null, processEventArgs);  // <-- This cannot be done since ProcessEventAsync is not virtual

To summarize, I see 2 options to mock EventProcessorClient

  1. Somehow mock EventHubConnection (and underlying TransportClient/AmqpClient)
  2. Make ProcessEventAsync virtual, so it can be redefined in a mock.

Are there any other options? Maybe I'm missing something?

@ghost ghost added needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team and removed needs-author-feedback Workflow: More information is needed from author to address the issue. labels Oct 1, 2022
@m-redding
Copy link
Member

Yes, in general the recommended approach is to avoid mocking the EventProcessorClient and to just test the handlers directly.

However, now that I have a better understanding of what you're trying to do here I think there are ways you could get around mocking the processor client in a complicated way. For example, the following code would simply call the handler when processing is started

Func<ProcessEventArgs, Task> processEventHandler = async (args) => 
{ 
    // your handler definition
};

mockProcessor.Setup(
    p => p.StartProcessingAsync(It.IsAny<CancellationToken>())).Callback(async () => { await processEventHandler(processEventArgs); } );

Configuring something like this would probably work for your scenario where it seems you're looking to abstract out all of the processor client behavior and test that your own application code works with the .NET host. Then you could have separate unit tests to cover the cases you need for your handler scenarios. Using live tests with Azure resources would obviously be the most helpful for testing the interaction between the processor and your application, but that obviously has a monetary cost.

@m-redding m-redding added needs-author-feedback Workflow: More information is needed from author to address the issue. and removed needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team labels Oct 3, 2022
@ghost ghost added the no-recent-activity There has been no recent activity on this issue. label Oct 11, 2022
@ghost
Copy link

ghost commented Oct 11, 2022

Hi, we're sending this friendly reminder because we haven't heard back from you in 7 days. We need more information about this issue to help address it. Please be sure to give us your input. If we don't hear back from you within 14 days of this comment the issue will be automatically closed. Thank you!

@denisivan0v
Copy link
Author

@m-redding Sorry for the delay with my response.

However, now that I have a better understanding of what you're trying to do here I think there are ways you could get around mocking the processor client in a complicated way. For example, the following code would simply call the handler when processing is started

I didn't quite understand how that is applicable to my scenario. Sure, I can mock StartProcessingAsync method (since it's virtual) to call my code when processing is started, but that's not what I want.

To clarify my goal - I'm trying to find a way to "feed" EventProcessorClient from some in-memory collection of events and allow my application to process these events. At the end of the test I want to verify that the application produced some output. Basically, it is a black box test - I start the app in test process, send some input to it and verify the output.

To make it easier, I've created a sample app here that uses EventProcessorClient to read events from EventHubs and buffer event payload to memory using simple IDataStorage abstraction.

public interface IDataStorage
{
    void Buffer(ReadOnlyMemory<byte> data);
}

In the integration test (the code is here) I simply override EventProcessorClient and IDataStorage instances in DI container.

private readonly Mock<IDataStorage> _storageMock = new();
private readonly Mock<EventProcessorClient> _processorMock = new();
    
public IntegrationTests(WebApplicationFactory<Program> factory)
{
    _storageMock.Setup(x => x.Buffer(It.IsAny<ReadOnlyMemory<byte>>())).Verifiable();
        
    _ = factory.WithWebHostBuilder(builder =>
    {
        builder.ConfigureTestServices(services =>
        {
            services.AddSingleton(_processorMock.Object);
            services.AddSingleton(_storageMock.Object);
        });
     }).CreateDefaultClient();
}

The main idea is to verify that the app called IDataStorage.Buffer at least once.
Can you please help me to make this test green?

@ghost ghost added needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team and removed needs-author-feedback Workflow: More information is needed from author to address the issue. no-recent-activity There has been no recent activity on this issue. labels Oct 12, 2022
@m-redding
Copy link
Member

Hi! No worries. I took a look at the code that you provided in the linked repo and the approach I mentioned above should work for what you're trying to do.

After discussing the need for having a simulated run of the EventProcessorClient we added this sample that also could be helpful for you: New Processor Mocking Sample.

To try and explain a little clearer for your specific scenario, you can use any in-memory collection of events that you want to when defining the callback for the mocked StartProcessingAsync. The approach I explained above was a simple example of a callback that you could define, just directly calling the handler once on a single set of event args. The linked sample shows how you could set up the mocked processor to fire a set of event args every second until StopProcessingAsync is called.

You can set up the mocked processor to start a timer when you call StartProcessingAsync. This timer will trigger a call to your handler directly at the frequency that makes sense for your integration test. Each call to the handler can be populated using the in-memory collection of events that you define here. This approach should simulate a whole processor run within your application, starting the processor, receiving events to be processed, and then stopping it.

@m-redding m-redding added needs-author-feedback Workflow: More information is needed from author to address the issue. and removed needs-team-attention Workflow: This issue needs attention from Azure service team or SDK team labels Oct 14, 2022
@ghost ghost added the no-recent-activity There has been no recent activity on this issue. label Oct 22, 2022
@ghost
Copy link

ghost commented Oct 22, 2022

Hi, we're sending this friendly reminder because we haven't heard back from you in 7 days. We need more information about this issue to help address it. Please be sure to give us your input. If we don't hear back from you within 14 days of this comment the issue will be automatically closed. Thank you!

@ghost ghost closed this as completed Nov 7, 2022
@github-actions github-actions bot locked and limited conversation to collaborators Mar 24, 2023
This issue was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Client This issue points to a problem in the data-plane of the library. customer-reported Issues that are reported by GitHub users external to the Azure organization. Event Hubs needs-author-feedback Workflow: More information is needed from author to address the issue. no-recent-activity There has been no recent activity on this issue. question The issue doesn't require a change to the product in order to be resolved. Most issues start as that
Projects
None yet
Development

No branches or pull requests

4 participants