Unit testing for a publish method in mass transit using in memory bus - rabbitmq

I am new to MassTransit and messaging. I am trying to write a unit test for a IBus.Publish and am not able to succeed with the result.
I am observing the fault from RabbitMQ and my observer looks like this:
public class FaultObserver : IReceiveObserver
{
public FaultObserver(IRequestUpdater statusUpdater,Lazy<IBus> bus)
{
this.statusUpdater = statusUpdater;
this.bus = bus;
}
public async Task ConsumeFault<T>(ConsumeContext<T> context, TimeSpan duration, string consumerType, Exception exception) where T : class
{
}
}
and my tests looks like the below code
var bus = fixture.Freeze<Mock<IBus>>();
bus.Setup(bu => bu.Publish<ReportFailedEvent>(It.IsAny<ReportFailedEvent>(),It.IsAny<CancellationToken>())).Verifiable();
var sut = fixture.Create<ReportRequestedFaultObserver>();
// Act
await sut.ConsumeFault(context.Object,new TimeSpan(0,0,1),string.Empty,exception);
// Assert
//bus.Verify(b => b.Publish<ReportFailedEvent>(It.IsAny<ReportFailedEvent>(), It.IsAny<CancellationToken>()), Times.Exactly(1));
bus.Verify(b =>b.Publish<ReportFailedEvent>(new ReportFailedEvent(request,exception.Message),It.IsAny<CancellationToken>()),Times.Once());
my setup looks like
[SetUp]
public void SetUp()
{
fixture.Customize(new AutoMoqCustomization());
var inMemoryTransportCache = new InMemoryTransportCache(Environment.ProcessorCount);
bus = Bus.Factory.CreateUsingInMemory(configure =>
{
configure.SetTransportProvider(inMemoryTransportCache);
configure.ReceiveEndpoint("input_queue", configurator =>
{
configurator.Handler<(cc =>
{
});
});
});
bus.Start().Ready.Wait();
}
I am not able to mock my Publish method. Does anyone knows what I am doing wrong?

Your mock for the bus is wrong. It should be:
bus.Setup(bu => bu.Publish<ReportFailedEvent>(
It.IsAny<object>(), It.IsAny<CancellationToken>()));
Publish takes an object and a CancellationToken, as per the interface definition in MassTransit:
Task Publish(object message, CancellationToken cancellationToken = default(CancellationToken));
Also, if you want to check the contents of the message, you can use the Moq Callback extension:
ReportFailedEvent message = null;
bus
.Setup(bu => bu.Publish<ReportFailedEvent>(It.IsAny<object>(), It.IsAny<CancellationToken>()))
.Callback<object, CancellationToken>((a, b) => { message = a as ReportFailedEvent; });
//.. your system under test code....
Assert.That(message.Property, Is.EqualTo(expectation));
If you are setting the priority, the MassTransit interface looks like this:
public static Task Publish<T>(
this IPublishEndpoint endpoint,
T message,
Action<PublishContext<T>> callback,
CancellationToken cancellationToken = default(CancellationToken))
where T : class;
And an example would be:
bus.Publish<ReportFailedEvent>(message, context =>
{
context.SetPriority(messagePriority.Priority);
});
And the accompanying Moq is:
ReportFailedEvent message = null;
bus
.Setup(bu => bu.Publish<ReportFailedEvent>(It.IsAny<object>(), It.IaAny<Action<PublishContext<ReportFailedEvent>>>(), It.IsAny<CancellationToken>()))
.Callback<object, Action<PublishContext<ReportFailedEvent>>, CancellationToken>((a, b, c) => { message = a as ReportFailedEvent; });
Just as a side note, ideally you should be publishing the interfaces rather than classes, so IReportFailedEvent rather than ReportFailedEvent.

Related

Why is JobConsumer not being hit/run?

I am trying out the new MassTransit IJobConsumer implementation, and although I've tried to follow the documentation, the JobConsumer I have written is never being run/hit.
I have:
created the JobConsumer which has a run method that runs the code I need it to
public class CalculationStartRunJobConsumer : IJobConsumer<ICalculationStartRun>
{
private readonly ICalculationRunQueue runQueue;
public CalculationStartRunJobConsumer(ICalculationRunQueue runQueue)
{
this.runQueue = runQueue;
}
public Task Run(JobContext<ICalculationStartRun> context)
{
return Task.Run(
() =>
{
var longRunningJob = new LongRunningJob<ICalculationStartRun>
{
Job = context.Job,
CancellationToken = context.CancellationToken,
JobId = context.JobId,
};
runQueue.StartSpecial(longRunningJob);
},
context.CancellationToken);
}
}
I have registered that consumer trying both ConnectReceiveEndpoint and AddConsumer
Configured the ServiceInstance as shown in the documentation
services.AddMassTransit(busRegistrationConfigurator =>
{
// TODO: Get rid of this ugly if statement.
if (consumerTypes != null)
{
foreach (var consumerType in consumerTypes)
{
busRegistrationConfigurator.AddConsumer(consumerType);
}
}
if(requestClientType != null)
{
busRegistrationConfigurator.AddRequestClient(requestClientType);
}
busRegistrationConfigurator.UsingRabbitMq((context, cfg) =>
{
cfg.UseNewtonsoftJsonSerializer();
cfg.UseNewtonsoftJsonDeserializer();
cfg.ConfigureNewtonsoftJsonSerializer(settings =>
{
// The serializer by default omits fields that are set to their default value, but this causes unintended effects
settings.NullValueHandling = NullValueHandling.Include;
settings.DefaultValueHandling = DefaultValueHandling.Include;
return settings;
});
cfg.Host(
messagingHostInfo.HostAddress,
hostConfigurator =>
{
hostConfigurator.Username(messagingHostInfo.UserName);
hostConfigurator.Password(messagingHostInfo.Password);
});
cfg.ServiceInstance(instance =>
{
instance.ConfigureJobServiceEndpoints(serviceCfg =>
{
serviceCfg.FinalizeCompleted = true;
});
instance.ConfigureEndpoints(context);
});
});
});
Seen that the queue for the job does appear in the queue for RabbitMQ
When I call .Send to send a message to that queue, it does not activate the Run method on the JobConsumer.
public async Task Send<T>(string queueName, T message) where T : class
{
var endpointUri = GetEndpointUri(messagingHostInfo.HostAddress, queueName);
var sendEndpoint = await bus.GetSendEndpoint(endpointUri);
await sendEndpoint.Send(message);
}
Can anyone help?
Software
MassTransit 8.0.2
MassTransit.RabbitMq 8.0.2
MassTransit.NewtonsoftJson 8.0.2
.NET6
Using in-memory for JobConsumer
The setup of any type of repository for long running jobs is missing. We needed to either:
explicitly specify that it was using InMemory (missing from the docs)
Setup saga repositories using e.g. EF Core.
As recommended by MassTransit, we went with the option of setting up saga repositories by implementing databases and interacting with them using EF Core.

MassTransit rabbitMq - Why are all my messages skipped

I am working on a .net core 3.1 app, for some reason my messages are not getting consumed.
service configuration :
services.AddMassTransit(x =>
{
x.AddConsumer<ItemAddedConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.UseHealthCheck(provider);
cfg.Host(new Uri($"rabbitmq://{rMqSettings.host}:{rMqSettings.port}"), h =>
{
h.Username(rMqSettings.username);
h.Password(rMqSettings.password);
});
cfg.ReceiveEndpoint("items-service.ItemAdded", ep =>
{
ep.ConfigureConsumeTopology=false;
ep.Bind("ItemAdded");
ep.PrefetchCount = 15;
ep.Consumer<ItemAddedConsumer>(provider);
});
consumer class :
public class ItemAddedConsumer : IConsumer<ItemAdded>
{
private readonly IMediator _mediator;
public ItemAddedConsumer(IMediator mediator)
{
_mediator = mediator;
}
public async Task Consume(ConsumeContext<ItemAdded> context)
{
await _mediator.Send(new ItemAdded(context.Message.Id));
}
}
and this is how i am sending the messages :
Uri uri = new Uri("exchange:ItemAdded?bind=true&queue=items-service.ItemAdded");
var endPoint = await _bus.GetSendEndpoint(uri);
await endPoint.Send(#event);
all messages get sent to a new queue called items-service.ItemAdded_skipped
queues
Make sure the sent message and the consumer are using the same message type, including namespace, as outlined in the docs.
Also, why the overly complicated send endpoint address and receive endpoint configuration? You can change the EntityName of the message (via attribute or the publish topology) and simply use Publish from your message producer.

Unit test Microsoft.Azure.ServiceBus TopicClient using Moq

I am trying to unit test Microsoft.Azure.ServiceBus(3.3.0) topic and subscription functionality. But I am not interested in testing Microsoft.Azure.ServiceBus classes, but more how mock Send a message to the topic and check if that message exists on that specific topic with a subscription.
At the moment I have a super simple Publisher class with one method SendAsync. As you can see here:
// Pseudo code, not full implementation!
public class Publisher : IPublisher
{
private readonly ManagementClient _managementClient;
private readonly TopicClientFactory _topicClientFactory;
public Publisher(ManagementClient managementClient, TopicClientFactory topicClientFactory)
{
_managementClient = managementClient;
_topicClientFactory = topicClientFactory;
}
public async Task SendAsync(myModel message)
{
ITopicClient topicClient = _topicClientFactory.Create("MyTopic");
// encode message using message
Message message = new Message(encodedMessage);
await topicClient.SendAsync(message); // trying to mock & test this!
await topicClient.CloseAsync();
}
}
Factory has only one method. When creating a new TopicClient using factory I am also returning the ITopicClient interface. Not sure if that helps.
// Pseudo code, not full implementation!
public class TopicClientFactory
{
public ITopicClient Create(string topicPath)
{
return new TopicClient("MyConnectionString", topicPath);
}
}
Unit test:
[Fact]
public async Task Name()
{
var managementClientMock = new Mock<ManagementClient>("MyConnectionString");
var topicClientFactoryMock = new Mock<TopicClientFactory>("MyConnectionString");
// mock topic client's send method!
var topicClientMock = new Mock<ITopicClient>();
topicClientMock.Setup(x =>
x.SendAsync(It.IsAny<Message>())).Returns(Task.CompletedTask); // .Verifiable();
// pass mocked topicClient to mocked factory
topicClientFactoryMock.Setup(tc => tc.Create("topicPath")).Returns(topicClientMock.Object);
var publisher = new Publisher(managementClientMock.Object, topicClientFactoryMock.Object);
await publisher.SendAsync(command);
// how to test if message has been sent?
}

Not receiving Terminated after Exception in Akka.net

I have two actors (a parent and a child) and I use SupervisorStrategy.StoppingStrategy and Watch to handle errors. But the problem is that the parent is not receiving a Terminated-message when the child dies.
Below is a simple test to verify my problem.
[Fact]
public void ExceptionTest()
{
var child = Sys.ActorOf(Props.Create(() => new FooActor(), SupervisorStrategy.StoppingStrategy), "Name");
Watch(child);
child.Tell("Some message");
ExpectMsg<Terminated>(); // This will not occur.
}
public class FooActor : ReceiveActor
{
public FooActor()
{
ReceiveAny((e) =>
{
// Crashes on every message.
throw new Exception();
});
}
}
What I want is that the parent which is watching the child should get a Terminated whenever the child has an error which it doesn't handle. How can I accomplish it?
It works fine when I send a PoisonPill instead of throwing an Exception.
Actors in tests seems to be created with restart supervising strategy and thus will not kill the actor when the exception happens. I can't find a way to change the supervisor strategy of tests. So I wrote this to create actors in tests with stopping supervisor strategy with out having to write a "real" wrapper actor.
[Fact]
public async Task ExceptionTest()
{
var actor = await CreateActorWithStoppingStrategy(Props.Create(() => new AsGoodAsDead()));
Watch(actor);
actor.Tell("Death");
ExpectTerminated(actor);
}
private async Task<IActorRef> CreateActorWithStoppingStrategy(Props props)
{
IActorRef child = null;
Action<IActorDsl> actor = d =>
{
d.Strategy = SupervisorStrategy.StoppingStrategy;
d.OnPreStart = context => child = context.ActorOf(props);
d.ReceiveAny((o, context) => context.Sender.Tell("ready"));
};
var parent = Sys.ActorOf(Props.Create(() => new Act(actor)));
await parent.Ask("ready?");
return child;
}
public class AsGoodAsDead : UntypedActor
{
protected override void OnReceive(object message)
{
throw new Exception();
}
}
The following line you wrote will give the child the stopping supervisor strategy, meaning the child's children will be stopped when something happens.
var child = Sys.ActorOf(Props.Create(() => new FooActor(), SupervisorStrategy.StoppingStrategy), "Name");

How do you wait/join on a WCF Web Service called from Silverlight?

If you call a web service from Silverlight like this:
MyServiceClient serviceClient = new MyServiceClient();
void MyMethod()
{
serviceClient.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(serviceClient_GetDataCompleted);
serviceClient.GetDataAsync();
// HOW DO I WAIT/JOIN HERE ON THE ASYNC CALL, RATHER THAN BEING FORCE TO LEAVE THIS METHOD?
}
I would rather wait/join with the asych service thread inside "MyMethod" rather than leaving "MyMethod" after calling "GetDataAsync", what is the best way to do this?
Thanks,
Jeff
No you cannot do this way. You will end up in a deadlock. GetDataCompleted is called by the mainthreed. The same threed thait is waiting in WaitOne.
I have to ask; why? The point is to provide your user with a fluid experience and waiting on a web service call will not necessarily do that. I suppose you want the full block of content to load before the Silverlight control loads. In that case, I would turn to caching the content rather than forcing the client to wait indefinitely.
To do this you would use a ManualResetEvent in your class (class level variable) and then wait on it.
void MyMethod()
{
wait = new ManualResetEvent(false);
// call your service
wait.WaitOne();
// finish working
}
and in your event handler code
void serviceClient_GetDataCompleted(...)
{
// Set values you need from service
wait.Set();
}
You could also use a lambda and closure to get similar behavior:
serviceClient.GetDataCompleted += (s,e) =>
{
// Your code here
};
serviceClient.GetDataAsync();
If you had a base class provide the mechanics of building a WCF channel, it could then be used to build the BeginX / EndX methods for a async call.
public class ServiceFooCoordinator : CoordinatorBase<IServiceFoo>
{
public IAsyncResult BeginMethodFoo ()
{
IAsyncResult ar = null;
IServiceFoo channel = null;
channel = _factory.GetChannel();
Begin( channel, () => ar = channel.BeginMethodFoo( null, channel ) );
return ar;
}
public Bar[] EndMethodFoo ( IAsyncResult ar )
{
IServiceFoo channel = null;
channel = _factory.GetChannel();
return channel.EndMethodFoo( ar );
}
}
Which can then be used in a method:
ServiceFooCoordinator _coordinator;
var asyncResult = _coordinator.BeginMethodFoo();
try
{
var result = _coordinator.EndMethodFoo( asyncResult );
}
catch ( Exception )
{ }
Which gets you your asynchronous call in a sychronous manner.