Faking Confluent .NET Kafka consumers - c#

Environment: .NET 4.5.
FakeItEasy : 4.0.0
I am trying to create a fake object of Kafka consumer, below is the syntax I use:
var fakeconsumer = A.Fake<Consumer<Null, string>>((x => x.WithArgumentsForConstructor(() => new Consumer<Null, string>(A.Dummy<IEnumerable<KeyValuePair<string, object>>>(), A.Dummy<IDeserializer<Null>>(), A.Dummy<StringDeserializer>()))));
The code for Kafka client is here: https://github.com/confluentinc/confluent-kafka-dotnet/blob/master/src/Confluent.Kafka/Consumer.cs
As you can see, I am invoking the Fake call with correct parameters required for the constructor. However I keep getting the follow error message : "No constructor matches the passed arguments for constructor.".
Any help is greatly appreciated.
Thank you
Edit:
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.CreateProxyGeneratorResult(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 125
at FakeItEasy.Creation.CastleDynamicProxy.CastleDynamicProxyGenerator.GenerateProxy(Type typeOfProxy, ProxyGenerationOptions options, IEnumerable1 additionalInterfacesToImplement, IEnumerable1 argumentsForConstructor, IFakeCallProcessorProvider fakeCallProcessorProvider) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\CastleDynamicProxy\CastleDynamicProxyGenerator.cs:line 86
at FakeItEasy.Creation.FakeObjectCreator.GenerateProxy(Type typeOfFake, IProxyOptions proxyOptions, IEnumerable1 argumentsForConstructor) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 113
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\FakeObjectCreator.cs:line 36
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\Creation\DefaultFakeAndDummyManager.cs:line 41
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder) in C:\projects\fakeiteasy\src\FakeItEasy\A.cs:line 47

I believe I've reproduced your problem. Here's the full exception that I see:
FakeItEasy.Core.FakeCreationException :
Failed to create fake of type Confluent.Kafka.Consumer`2[Confluent.Kafka.Null,System.String] with the specified arguments for the constructor:
No constructor matches the passed arguments for constructor.
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
at Confluent.Kafka.Consumer..ctor(IEnumerable`1 config)
at Confluent.Kafka.Consumer`2..ctor(IEnumerable`1 config, IDeserializer`1 keyDeserializer, IDeserializer`1 valueDeserializer)
at Castle.Proxies.Consumer`2Proxy..ctor(IInterceptor[] , IEnumerable`1 , IDeserializer`1 , IDeserializer`1 )
at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithArgumentsForConstructor(Type typeOfFake, String reasonForFailure)
at FakeItEasy.Creation.FakeObjectCreator.AssertThatProxyWasGeneratedWhenArgumentsForConstructorAreSpecified(Type typeOfFake, ProxyGeneratorResult result, IProxyOptions proxyOptions)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, IProxyOptions proxyOptions, DummyCreationSession session, IDummyValueResolver resolver, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, Action`1 optionsBuilder)
at FakeItEasy.A.Fake[T](Action`1 optionsBuilder)
Kafka.cs(14,0): at FakeItEasyQuestions2015.Kafka.MakeConsumer()
You can see that FakeItEasy itself encountered an exception while calling the Consumer class's constructor:
An exception of type System.ArgumentException was caught during this call. Its message was:
'group.id' configuration parameter is required and was not specified.
This was thrown from the Consumer constructor on line 756:
if (config.FirstOrDefault(prop => string.Equals(prop.Key, "group.id", StringComparison.Ordinal)).Value == null)
{
throw new ArgumentException("'group.id' configuration parameter is required and was not specified.");
}
It seems that
Consumer(IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)`
Has some requirements on its inputs that aren't being met. In particular, it seems it needs config to contain one element with the key "group.id". If I change your code to
var fakeconsumer = A.Fake<Consumer<Null, string>>(
(x => x.WithArgumentsForConstructor(
() => new Consumer<Null, string>(new [] { new KeyValuePair<string, object>("group.id", "hippo")},
A.Dummy<IDeserializer<Null>>(),
A.Dummy<StringDeserializer>()))));
The fake is created.
I notice that you cross-posted to FakeItEasy Issue 1176. I'll make a note there to come here for this answer.

Not 100% related to the original question but in my library (Silverback: https://github.com/BEagle1984/silverback) I have a mocked in-memory implementation of the Confluent.Kafka library, that allows for kinda sophisticated integration tests. See some simple examples: https://silverback-messaging.net/concepts/broker/testing.html.
Just to give you an idea:
[Fact]
public async Task SampleTest()
{
// Arrange
var testingHelper = _factory.Server.Host.Services
.GetRequiredService<IKafkaTestingHelper>();
var producer = testingHelper.Broker
.GetProducer(new KafkaProducerEndpoint("tst-topic"));
// Act
await producer.ProduceAsync(new TestMessage { Content = "abc" });
await testingHelper.WaitUntilAllMessagesAreConsumedAsync();
// Assert
testingHelper.Spy.OutboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes.Should().HaveCount(1);
testingHelper.Spy.InboundEnvelopes[0].Message.As<TestMessage>
.Content.Should().Be("abc");
}
The implementation is not that complex but it supports partitions and a simulation of the rebalance mechanism.
See the implementation: https://github.com/BEagle1984/silverback/tree/master/src/Silverback.Integration.Kafka.Testing/Messaging/Broker/Kafka

I am no expert on the Consumer class for Kafka, but it looks like your are invoking it like:
Consumer<Null, string>
But the only constructors I can find in the code are:
public Consumer(
IEnumerable<KeyValuePair<string, object>> config)
public Consumer(
IEnumerable<KeyValuePair<string, object>> config,
IDeserializer<TKey> keyDeserializer,
IDeserializer<TValue> valueDeserializer)
So there is no match. It looks like you want to use the first one, so you are missing the IEnumerable part.

Related

The entry point exited without ever building an IHost

I try to run integration tests and get this error
System.InvalidOperationException: The entry point exited without ever building an IHost.
at Microsoft.Extensions.Hosting.HostFactoryResolver.HostingListener.CreateHost()
at Microsoft.Extensions.Hosting.HostFactoryResolver.<>c__DisplayClass10_0.b__0(String[] args)
at Microsoft.AspNetCore.Mvc.Testing.DeferredHostBuilder.Build()
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateHost(IHostBuilder builder) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.ConfigureHostBuilder(IHostBuilder hostBuilder)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.EnsureServer() at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateDefaultClient(DelegatingHandler[] handlers)
at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateDefaultClient(Uri baseAddress, DelegatingHandler[] handlers) at Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory1.CreateClient(WebApplicationFactoryClientOptions options)
at Ptco.System.IntegrationTests.Infrastructure.IntegrationTestsWebFactory.CreteManagedClient() in C:\Users\nemes\Documents\GitHub\ptco.app\System\Ptco.System.IntegrationTests\Infrastructure\IntegrationTestsWebFactory.cs:line 249
Row 249 is
private HttpClient CreteManagedClient() =>
CreateClient(new WebApplicationFactoryClientOptions
{
BaseAddress = new Uri(_configuration.GetValue<string>("IntegrationServerBaseUri"))
});
That is called like this
public IntegrationTestsWebFactory()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
{
_configurationPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "appsettings.json");
_configuration = BuildConfiguration();
ManagedHttpClient = CreteManagedClient();
}
How I can solve this error?
It happens to me because I use serilog with CreateBootstrapLogger().
Here is the detail discussion https://github.com/serilog/serilog-aspnetcore/issues/289
To me, it works fine with CreateLogger. Check if really need CreateBootstrapLogger here https://nblumhardt.com/2020/10/bootstrap-logger/#why-cant-we-just-do-both

Singleton saga in MassTransit

What I'm trying to do is to create only one saga instance, not updated by subsequent events. So for example if I send EventA at 09:00, with CorrelationId equal to 979158a2-dfa0-45f0-9107-8b8b8028bb9f and then the saga for that event is still alive at 10:00, then if at 10:00 I send another instance of EventA with the same CorrelationId that was sent previously, I don't want it to overwrite the previous saga state but instead I would like this event to be ignored by MT. I kind of achieved that, because my saga state is not overriden (only its version is incremented), but the problem is that MT throws an error when the new event arrives. The exception is (+one log entry above it):
[18:41:12 DBG] SAGA:App.Services.Communicators.Amazon.Dispatcher.Api.Sagas.GetOrdersState:0368a723-a819-450f-b6e5-9211d1d6a3a9 Dupe App.Services.Financial.Contracts.GetOrdersCommand
MongoDB.Driver.MongoWriteException: A write operation resulted in an error.
E11000 duplicate key error collection: Dispatcher.get.orders.states index: _id_ dup key: { _id: BinData(3, 23A7680319A80F45B6E59211D1D6A3A9) }
---> MongoDB.Driver.MongoBulkWriteException`1[App.Services.Communicators.Amazon.Dispatcher.Api.Sagas.GetOrdersState]: A bulk write operation resulted in one or more errors.
E11000 duplicate key error collection: Dispatcher.get.orders.states index: _id_ dup key: { _id: BinData(3, 23A7680319A80F45B6E59211D1D6A3A9) }
at MongoDB.Driver.MongoCollectionImpl`1.BulkWriteAsync(IClientSessionHandle session, IEnumerable`1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionImpl`1.UsingImplicitSessionAsync[TResult](Func`2 funcAsync, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionBase`1.InsertOneAsync(TDocument document, InsertOneOptions options, Func`3 bulkWriteAsync)
--- End of inner exception stack trace ---
at MongoDB.Driver.MongoCollectionBase`1.InsertOneAsync(TDocument document, InsertOneOptions options, Func`3 bulkWriteAsync)
at MassTransit.MongoDbIntegration.Saga.Context.MongoDbSagaRepositoryContext`2.Insert(TSaga instance)
And the important part of the saga config:
Event(() => GetOrdersIntegrationEventReceived, e =>
e
.CorrelateById(x => x.Message.CorrelationId)
.SelectId(x => x.Message.CorrelationId)
.SetSagaFactory(ctx => new GetOrdersState
{
CorrelationId = ctx.Message.CorrelationId,
PartnerId = ctx.Message.PartnerId,
LastUpdatedBeforeFromRequest = ctx.Message.LastUpdatedBefore
})
.InsertOnInitial = true);
Is what I'm trying to do even possible?
MassTransit will only create one instance of the saga for each CorrelationId, so your reasoning is correct. However, you're approach is a little off and could use some tweaking.
For instance, your event configuration:
Event(() => GetOrdersIntegrationEventReceived, e => e.CorrelateById(x => x.Message.CorrelationId));
That's it, all the extra stuff is why you're seeing messages in the debug log.
Then, after the event is declared:
During(Initial, Created,
When(GetOrdersIntegrationEventReceived)
.Then(context =>
{
context.Instance.PartnerId = context.Message.PartnerId,
context.Instance.LastUpdatedBeforeFromRequest = context.Message.LastUpdatedBefore
})
.TransitionTo(Created)
);
That's it.

Controller test fails only on server

So I built unit tests for an ApiController, and one of those fails only on the server in the CI pipeline with the exception System.InvalidOperationException : HttpControllerContext.Configuration must not be null.
The structure looks like this:
public IHttpActionResult MyControllerMethod()
{
if(this.SomeThings()) // uses this.IService and this.IService2
return this.UnAuthorized();
DoOtherStuff();
return this.Ok();
}
In my test, I mock IService and IService2 and pass them to the controller:
var controllerUnderTest = new MyController(serviceMock, serviceMock2);
Then I call the method with some invalid data, provoking the return of UnAuthorized():
var result = await controllerUnderTest.MyControllerMethod(invalidData).ExecuteAsync(CancellationToken.None);`
At first, this failed. So I added
controllerUnderTest.Configuration = new HttpConfiguration();
controllerUnderTest.Request = new HttpRequestMessage();
from this Stackoverflow post.
So now it works. Locally. It works when debugging, it works using ReSharper's testrunner, it works using NCrunch.
But it doesn't work in the pipeline.
When executed in the pipeline using ncrunch console tool, the tests fails with above message.
Any idea how to fix this? How can config be nul when I explicitely initialize it?
Edit: Here is the stacktrace:
System.InvalidOperationException : HttpControllerContext.Configuration must not be null.
bei System.Web.Http.Results.ExceptionResult.ApiControllerDependencyProvider.EnsureResolved()
bei System.Web.Http.Results.ExceptionResult.ApiControllerDependencyProvider.get_IncludeErrorDetail()
bei System.Web.Http.Results.ExceptionResult.Execute()
bei System.Web.Http.Results.ExceptionResult.ExecuteAsync(CancellationToken cancellationToken)

How to check that an error is logged in a unit test for ASP.NET Core 3.0?

I want to create a unit test to ensure that a method is logging an error using xUnit and Moq. This code worked in ASP.NET Core 2.1:
//Arrange
var logger = new Mock<ILogger<MyMiddleware>>();
var httpContext = new DefaultHttpContext();
var middleware = new MyMiddleware(request => Task.FromResult(httpContext), logger.Object);
//Act
await middleware.InvokeAsync(httpContext);
//Assert
logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<FormattedLogValues>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()), Times.Once);
To validate that _logger.LogError("Error message"); was called in middleware.InvokeAsync.
However, in ASP.NET Core 3.0, I am unable to verify that the logger is being called. Microsoft.Extensions.Logging.Internal can no longer be referenced, so FormattedLogValues is unavailable.
I tried changing the Assert() to use object instead of FormattedLogValues, and also IReadOnlyList<KeyValuePair<string, object>> since that is what FormattedLogValues is based on (FormattedLogValues.cs).
This is the error message I am getting in the Visual Studio test runner:
Message:
Moq.MockException :
Expected invocation on the mock once, but was 0 times: x => x.Log<object>(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<object>(), It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>())
Performed invocations:
ILogger.Log<FormattedLogValues>(LogLevel.Error, 0, Error message, null, Func<FormattedLogValues, Exception, string>)
Stack Trace:
Mock.Verify(Mock mock, LambdaExpression expression, Times times, String failMessage)
Mock`1.Verify(Expression`1 expression, Times times)
Mock`1.Verify(Expression`1 expression, Func`1 times)
MyMiddlewareTests.InvokeAsync_ErrorIsLogged() line 35
--- End of stack trace from previous location where exception was thrown ---
How can I validate the error is being logged in ASP.NET Core 3.0?
There is an issue on the aspnet github page about this. It seems the problem is with Moq and they have made changes to fix it.
You will need to upgrade to Moq 4.13 to get the fix.
They have introduced an It.IsAnyType to resolve the problem with internal objects so you should be able to change the object reference to It.IsAnyType and write your test like this:
logger.Verify(x => x.Log(LogLevel.Error, It.IsAny<EventId>(), It.IsAny<It.IsAnyType>(), It.IsAny<Exception>(), (Func<It.IsAnyType, Exception, string>)It.IsAny<object>()), Times.Once);
Note: The last parameter needs to be type cast as Moq doesn't currently support nested type matchers.
More details from Moq can be found here
This is a bit late, but I just want to add that if you are asserting for specific messages being logged, you can create a custom matcher that inspects the message:
logger.Verify(x => x.Log(LogLevel.Error,
It.IsAny<EventId>(),
It.Is<It.IsAnyType>((x, _) => LogMessageMatcher(x, "Expected error message")),
It.IsAny<Exception>(),
It.IsAny<Func<It.IsAnyType, Exception, string>>()), Times.Once);
The MatcherMethod would be a static method like so:
public static bool LogMessageMatcher(object formattedLogValueObject, string message)
{
var logValues = formattedLogValueObject as IReadOnlyList<KeyValuePair<string, object>>;
return logValues.FirstOrDefault(logValue => logValue.Key == "{OriginalFormat}")
.Value.ToString() == message;
}
I use SGA (Setup, Grab, Assert) approach.
//In the setup part
var mockLog = new Mock<ILogger>(MockBehavior.Strict);
string error = null;
mockLog.Setup(l => l
.Error(It.IsAny<Exception>(), It.IsAny<string>()))
.Callback((Exception b, string c) =>
{
error = c + " " + b.GetBaseException().Message;
// This would keep only the last error, but it's OK, since there should be zero
// Sometimes I use a collection and append the error info.
});
//In the assert part
Assert.IsNull(error, $"Error detected: {error}");

Molling static method with generic return type <T>

I’m trying to implement on a very big and bloating system a framework to use unit testing, the main problem is that 70% of the system is implemented with static methods. Is how I’ve reached Moles, and I’m very confident that I will reach my goal (I’m quite near).
The problem I have right now is that I’m trying to mole a static method with a Generic Parameter:
public static T ExecScalar<T>(IDbConnection cx, string commandText, IDbTransaction tx, IEnumerable<IDbDataParameter> parameters)
googling on the web I’ve found this forum message:
http://social.msdn.microsoft.com/Forums/uk/pex/thread/3faadca2-a26f-449c-942e-d800a6079e02
that is exactly my problem, and there is suggested to read the manual at page 10.
I’ve read the manual at page 10, but it’s talking about stubbing and not molling. I ‘ve tried to apply the described solution but if I put:
Ktcar.Cs.Dal.DB.Moles.MDALDB.ExecScalarIDbConnectionStringIDbTransactionIEnumerableOfIDbDataParameter<int> = delegate(IDbConnection conn, String command, IDbTransaction trx, IEnumerable<IDbDataParameter> parameters)
I get:
“Only assignment, call, increment, decrement, and new object expressions can be used as a statement”
I have also tried:
Ktcar.Cs.Dal.DB.Moles.MDALDB.ExecScalarIDbConnectionStringIDbTransactionIEnumerableOfIDbDataParameter<T> = delegate(IDbConnection conn, String command, IDbTransaction trx, IEnumerable<IDbDataParameter> parameters)
And I get the same error
Or:
Ktcar.Cs.Dal.DB.Moles.MDALDB.ExecScalarIDbConnectionStringIDbTransactionIEnumerableOfIDbDataParameter = null;
And then I get:
“Cannot assign to 'ExecScalarIDbConnectionStringIDbTransactionIEnumerableOfIDbDataParameter' because it is a 'method group'”
please, can anyone helpme on how to mock a static method with Generic Type output?
Try this one:
Ktcar.Cs.Dal.DB.Moles.MDALDB.ExecScalarIDbConnectionStringIDbTransactionIEnumera‌​bleOfIDbDataParameter<int>((IDbConnection conn, String command, IDbTransaction trx, IEnumerable<IDbDataParameter> parameters) => { return 5; });

Categories

Resources