I have a .net micro-service receiving messages using RabbitMQ client, I need to test the following:
1- consumer is successfully connected to rabbitMq host.
2- consumer is listening to queue.
3- consumer is receiving messages successfully.
To achieve the above, I have created a sample application that sends messages and I am debugging consumer to be sure that it is receiving messages.
How can I automate this test? hence include it in my micro-service CI.
I am thinking to include my sample app in my CI so I can fire a message then run a consumer unit test that waits a specific time then passes if the message received, but this seems like a wrong practice to me because the test will not start until a few seconds the message is fired.
Another way I am thinking of is firing the sample application from the unit test itself, but if the sample app fails to work that would make it the service fault.
Is there any best practices for integration testing of micro-services connecting through RabbitMQ?
I have built many such tests. I have thrown up some basic code on
GitHub here with .NET Core 2.0.
You will need a RabbitMQ cluster for these automated tests. Each test starts by eliminating the queue to ensure that no messages already exist. Pre existing messages from another test will break the current test.
I have a simple helper to delete the queue. In my applications, they always declare their own queues, but if that is not your case then you'll have to create the queue again and any bindings to any exchanges.
public class QueueDestroyer
{
public static void DeleteQueue(string queueName, string virtualHost)
{
var connectionFactory = new ConnectionFactory();
connectionFactory.HostName = "localhost";
connectionFactory.UserName = "guest";
connectionFactory.Password = "guest";
connectionFactory.VirtualHost = virtualHost;
var connection = connectionFactory.CreateConnection();
var channel = connection.CreateModel();
channel.QueueDelete(queueName);
connection.Close();
}
}
I have created a very simple consumer example that represents your microservice. It runs in a Task until cancellation.
public class Consumer
{
private IMessageProcessor _messageProcessor;
private Task _consumerTask;
public Consumer(IMessageProcessor messageProcessor)
{
_messageProcessor = messageProcessor;
}
public void Consume(CancellationToken token, string queueName)
{
_consumerTask = Task.Run(() =>
{
var factory = new ConnectionFactory() { HostName = "localhost" };
using (var connection = factory.CreateConnection())
{
using (var channel = connection.CreateModel())
{
channel.QueueDeclare(queue: queueName,
durable: false,
exclusive: false,
autoDelete: false,
arguments: null);
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
_messageProcessor.ProcessMessage(message);
};
channel.BasicConsume(queue: queueName,
autoAck: false,
consumer: consumer);
while (!token.IsCancellationRequested)
Thread.Sleep(1000);
}
}
});
}
public void WaitForCompletion()
{
_consumerTask.Wait();
}
}
The consumer has an IMessageProcessor interface that will do the work of processing the message. In my integration test I created a fake. You would probably use your preferred mocking framework for this.
The test publisher publishes a message to the queue.
public class TestPublisher
{
public void Publish(string queueName, string message)
{
var factory = new ConnectionFactory() { HostName = "localhost", UserName="guest", Password="guest" };
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
var body = Encoding.UTF8.GetBytes(message);
channel.BasicPublish(exchange: "",
routingKey: queueName,
basicProperties: null,
body: body);
}
}
}
My example test looks like this:
[Fact]
public void If_SendMessageToQueue_ThenConsumerReceiv4es()
{
// ARRANGE
QueueDestroyer.DeleteQueue("queueX", "/");
var cts = new CancellationTokenSource();
var fake = new FakeProcessor();
var myMicroService = new Consumer(fake);
// ACT
myMicroService.Consume(cts.Token, "queueX");
var producer = new TestPublisher();
producer.Publish("queueX", "hello");
Thread.Sleep(1000); // make sure the consumer will have received the message
cts.Cancel();
// ASSERT
Assert.Equal(1, fake.Messages.Count);
Assert.Equal("hello", fake.Messages[0]);
}
My fake is this:
public class FakeProcessor : IMessageProcessor
{
public List<string> Messages { get; set; }
public FakeProcessor()
{
Messages = new List<string>();
}
public void ProcessMessage(string message)
{
Messages.Add(message);
}
}
Additional advice is:
If you can append randomized text to your queue and exchange names on each test run then do so to avoid concurrent tests interfering with each other
I have some helpers in the code for declaring queues, exchanges and bindings also, if your applications don't do that.
Write a connection killer class that will force close connections and check your applications still work and can recover. I have code for that, but not in .NET Core. Just ask me for it and I can modify it to run in .NET Core.
In general, I think you should avoid including other microservices in your integration tests. If you send a message from one service to another and expect a message back for example, then create a fake consumer that can mock the expected behaviour. If you receive messages from other services then create fake publishers in your integration test project.
I was successfully doing such kind of test. You need test instance of RabbitMQ, test exchange to send messages to and test queue to connect to receive messages.
Do not mock everything!
But, with test consumer, producer and test instance of rabbitMQ there is no actual production code in that test.
use test rabbitMQ instance and real aplication
In order to have meaniningfull test I would use test RabbitMQ instance, exchange and queue, but leave real application (producer and consumer).
I would implement following scenario
when test application does something that test message to rabbitMQ
then number of received messages in rabbitMQ is increased then
application does something that it should do upon receiving messages
Steps 1 and 3 are application-specific. Your application sends messages to rabbitMQ based on some external event (HTTP message received? timer event?). You could reproduce such condition in your test, so application will send message (to test rabbitMQ instance).
Same story for verifying application action upon receiving message. Application should do something observable upon receiving messages.
If application makes HTTP call- then you can mock that HTTP endpoint and verify received messages. If application saves messages to the database- you could pool database to look for your message.
use rabbitMQ monitoring API
Step 2 can be implemented using RabbitMQ monitoring API (there are methods to see number of messages received and consumed from queue https://www.rabbitmq.com/monitoring.html#rabbitmq-metrics)
consider using spring boot to have health checks
If you are java-based and then using Spring Boot will significantly simpify your problem. You will automatically get health check for your rabbitMQ connection!
See https://spring.io/guides/gs/messaging-rabbitmq/ for tutorial how to connect to RabbitMQ using Spring boot.
Spring boot application exposes health information (using HTTP endpoint /health) for every attached external resource (database, messaging, jms, etc)
See https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html#_auto_configured_healthindicators for details.
If connection to rabbitMQ is down then health check (done by org.springframework.boot.actuate.amqp.RabbitHealthIndicator) will return HTTP code 4xx and meaninfull json message in JSON body.
You do not have to do anything particular to have that health check- just using org.springframework.boot:spring-boot-starter-amqp as maven/gradle dependency is enough.
CI test- from src/test directory
I have written such test (that connect to external test instance of RabbitMQ) using integration tests, in src/test directory. If using Spring Boot it is easiest to do that using test profile, and having details of connection to test RabbitMQ instance in application-test.properties (production could use production profile, and application-production.properties file with production instance of RabbitMQ).
In simplest case (just verify connection to rabbitMQ) all you need is to start application normally and validate /health endpoint.
In this case I would do following CI steps
one that builds (gradle build)
one that run unit tests (tests without any external dependenices)
one that run integration tests
CI test- external
Above described approach could also be done for application deployed to test environment (and connected to test rabbitMQ instance). As soon as application starts, you can check /health endpoint to make sure it is connected to rabbitMQ instance.
If you make your application send message to rabbitMQ, then you could observe rabbbitMQ metrics (using rabbitMQ monitoring API) and observe external effects of message being consumed by application.
For such test you need to start and deploy your application from CI befor starting tests.
for that scenario I would do following CI steps
step that that builds app
steps that run all tests in src/test directory (unit, integration)
step that deploys app to test environment, or starts dockerized application
step that runs external tests
for dockerized environment, step that stops docker containers
Consider dockerized enevironment
For external test you could run your application along with test RabbitMQ instance in Docker. You will need two docker containers.
one with application
one with rabbitMQ . There is official docker image for rabbitmq https://hub.docker.com/_/rabbitmq/ and it is really easy to use
To run those two images, it is most reasonable to write docker-compose file.
Related
I am experimenting with using NServiceBus with MSMQ as transport. A very simple example:
static void Main(string[] args)
{
var endpointConfiguration = new EndpointConfiguration("myappqueue");
endpointConfiguration.UseTransport<MsmqTransport>();
var endpointInstance = Endpoint.Start(endpointConfiguration).Result;
Console.ReadKey();
endpointInstance.Stop();
}
I have added the Windows feature Message Queue in and created a private queue called myappqueue.
When I run the application and get to the line with Endpoint.Start, I get this error:
Faults forwarding requires an error queue to be specified using 'EndpointConfiguration.SendFailedMessagesTo()'
What am I missing? This configuration is not mentioned in the samples on Particular's documentation site.
When an endpoint is created and operational, messages can fail processing. In that case, NServiceBus needs to forward failed messages to the designated error queue which you need to specify. EndpointConfiguration.SendFailedMessagesTo() is the API to use to configure what error queue to use.
You mind find this documentaiton helpful when configuring your endpoint for error handling. And since you're new to NServiceBus, tutorials can be helpful as well.
When I switch from non-durable to durable topic subscriber, I am unable to look up the topic name that I could read before (using JNDI).
It gives an error in the admin console as the topic is being looked up:
An error occurred during activation of changes, please see the log for details.
ERROR: Could not activate itft-jmsmodule!ITFT-JMS-1#ItftTopic
The Messaging Kernel ITFT-JMS-1 has not yet been opened
I am using Oracle WebLogic Server Administrative Console to set up the WebSphere queue. On the console, I made these changes:
For the Persistent Stores, On the Configuration tab, Added a file store called ItftFileStore
For the Persistent Stores, On the Configuration tab, Added a directory.
For the JMS Servers, On the Configuration -> General tab, Changed the Persistent Store to ItftFileStore
For the JMS Servers, On the Configuration -> General tab -> Advanced, Checked the Store Enabled field.
For the ItftTopic, Configuration -> Override tab, Changed Delivery Mode Override to Persistent.
This is the code which I am running. There are some comments on the pertinent lines.
public void start() throws Exception {
try {
LOG.info("Starting the FC MQ message consumer / listener ...");
InitialContext initialContext = getInitialContext();
topicConnectionFactory = (TopicConnectionFactory) initialContext.lookup(jmsFactory);
topicConnection = topicConnectionFactory.createTopicConnection();
topicConnection.setClientID(clientId);
LOG.info("1"+topicConnection.getClientID());
topicSession = topicConnection.createTopicSession(false, Session.CLIENT_ACKNOWLEDGE);
LOG.info("2"+topicConnection.getClientID());
//topicConnection.setExceptionListener(connectionExceptionListener);
jmsTopic = (Topic) initialContext.lookup(topic); // Error being thrown here
LOG.info("3"+topicConnection.getClientID());
//topicSubscriber = topicSession.createSubscriber(jmsTopic); // Works as a non-durable subscriber
topicSession.createDurableSubscriber(jmsTopic,subscriberName);
LOG.info("4"+topicConnection.getClientID());
topicSubscriber.setMessageListener(messageListener);
topicConnection.start();
The fundamental aspect of the problem is that you are connecting WebLogic to a Websphere JMS topic, this has become clear with the last edit of your question but it is not clear whether you are using WebLogic Messaging Bridge or not. The Messaging Bridge is the proper way of configuring a foreign JMS server in WebLogic. I suggest reading this FAQ and this how-to that is specific for Websphere.
I have registered AsyncHornetQHTWorkItemHandler with KnowledgeRuntime
AsyncHornetQTaskClient asyncHornetQTaskClient=new AsyncHornetQTaskClient("Async-Task-ProcEngine-Client");
AsyncHornetQHTWorkItemHandler asyncHornetQHTWorkItemHandler = new AsyncHornetQHTWorkItemHandler(asyncHornetQTaskClient,knowledgeSession, OnErrorAction.RETHROW);
asyncHornetQHTWorkItemHandler.setPort(5445);
asyncHornetQHTWorkItemHandler.setIpAddress("localhost");
logger.debug("Asycnhronous work-item-handler connecting....");
asyncHornetQHTWorkItemHandler.connect();
logger.debug("Asycnhronous work-item-handler connected.");
knowledgeSession.getWorkItemManager().registerWorkItemHandler("Human Task", asyncHornetQHTWorkItemHandler);
BaseHornetQTaskServer is configured to use the standalone HornetQ server running # port 5445
public CustomHornetQTaskServer(TaskService service, String host, int port) {
super(new HornetQTaskServerHandler(service, SystemEventListenerFactory.getSystemEventListener()), host, port, true);
}
Human Tasks are created successfully in Task database when a process instance is created.
I am using AsyncHornetQTaskClient (wrapped with SyncTaskServiceWrapper) from my web application to act (complete,skip, etc) on the Tasks.
TaskService client = new SyncTaskServiceWrapper(new AsyncHornetQTaskClient("Async-Task-Client"));
client.connect("127.0.0.1", 5445);
return client;
The process engine is hosted in a separate VM to run as a daemon/background service.
Human-task service is embedded in my web-application hosted in Jboss 7.
How KnowledgeRuntime.WorkItemManager in the daemon service will get the event that work-item has completed ?
Should I explicity call KnowledgeRuntime.getWorkItemManager().completeWorkItem ?
Environment : JBPM 5.4.0.Final, HornetQ 2.2.14.Final, Jboss 7
I'm having a hard time trying to get my task to stay persistent and run indefinitely from a WCF service. I may be doing this the wrong way and am willing to take suggestions.
I have a task that starts to process any incoming requests that are dropped into a BlockingCollection. From what I understand, the GetConsumingEnumerable() method is supposed to allow me to persistently pull data as it arrives. It works with no problem by itself. I was able to process dozens of requests without a single error or flaw using a windows form to fill out the request and submit them. Once I was confident in this process I wired it up to my site via an asmx web service and used jQuery ajax calls to submit request.
The site submits request based on a url that is submitted, the Web Service downloads the html content from the url and looks for other urls within the content. It then proceeds to create a request for each url it finds and submits it to the BlockingCollection. Within the WCF service, if the application is Online (i.e. Task has started) - it pulls the request using the GetConsumingEnumerable via a Parallel.ForEach and Processes the request.
This works for the first few submissions, but then the task just stops unexpectedly. Of course, this is doing 10x more request than I could simulate in testing - but I expected it to just throttle. I believe the issue is in my method that starts the task:
public void Start()
{
Online = true;
Task.Factory.StartNew(() =>
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 20;
options.CancellationToken = token;
try
{
Parallel.ForEach(FixedWidthQueue.GetConsumingEnumerable(token), options, (request) =>
{
Process(request);
options.CancellationToken.ThrowIfCancellationRequested();
});
}
catch (OperationCanceledException e)
{
Console.WriteLine(e.Message);
return;
}
}, TaskCreationOptions.LongRunning);
}
I've thought about moving this into a WF4 Service and just wire it up in a Workflow and use Workflow Persistence, but am not willing to learn WF4 unless necessary. Please let me know if more information is needed.
The code you have shown is correct by itself.
However there are a few things that can go wrong:
If an exception occurs, your task stops (of course). Try adding a try-catch and log the exception.
If you start worker threads in a hosted environment (ASP.NET, WCF, SQL Server) the host can decide arbitrarily (without reason) to shut down any worker process. For example, if your ASP.NET site is inactive for some time the app is shut down. The hosts that I just mentioned are not made to have custom threads running. Probably, you will have more success using a dedicated application (.exe) or even a Windows Service.
It turns out the cause of this issue was with the WCF Binding Configuration. The task suddenly stopped becasue the WCF killed the connection due to a open timeout. The open timeout setting is the time that a request will wait for the service to open a connection before timing out. In certain situations, it reached the limit of 10 max connection and caused the incomming connections to get backed up waiting for a connection. I made sure that I closed all connections to the host after the transactions were complete - so I gave in to upping the max connections and the open timeout period. After this - it ran flawlessly.
My machine is Windows 7 ultimate (64 bit). I have installed MSMQ and checked that it is working fine (ran some sample codes for MSMQ).
When i try to create a WCF Service using MsmqIntegrationBinding class, i get the below exception:
"An error occurred while opening the queue:The queue does not exist or you do not have sufficient permissions to perform the operation. (-1072824317, 0xc00e0003). The message cannot be sent or received from the queue. Ensure that MSMQ is installed and running. Also ensure that the queue is available to open with the required access mode and authorization."
I am running the visual studio in Administrator mode and explicitly grant permission to myself via a URL ACL using:
netsh http add urlacl url=http://+:80/ user=DOMAIN\user
Below is the code:
public static void Main()
{
Uri baseAddress = new Uri(#"msmq.formatname:DIRECT=OS:AJITDELL2\private$\Orders");
using (ServiceHost serviceHost = new ServiceHost(typeof(OrderProcessorService), baseAddress))
{
MsmqIntegrationBinding serviceBinding = new MsmqIntegrationBinding();
serviceBinding.Security.Transport.MsmqAuthenticationMode = MsmqAuthenticationMode.None;
serviceBinding.Security.Transport.MsmqProtectionLevel = System.Net.Security.ProtectionLevel.None;
//serviceBinding.SerializationFormat = MsmqMessageSerializationFormat.Binary;
serviceHost.AddServiceEndpoint(typeof(IOrderProcessor), serviceBinding, baseAddress);
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The service is ready.");
Console.WriteLine("The service is running in the following account: {0}", WindowsIdentity.GetCurrent().Name);
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
// Close the ServiceHostBase to shutdown the service.
serviceHost.Close();
}
}
Can you please help?
Make sure you have created the "Orders" queue in MSMQ.
In Windows Server 2008, you can do so from the Server Manager (right click on My Computer and select Manage), then Features -> Message Queuing -> Private Queues. Right click on Private Queues and add your "Orders" queue there.
You may also want to check Nicholas Allen's article: Diagnosing Common Queue Errors. It suggests that your error can only be: "that the queue does not exist, or perhaps you've specified the queue name incorrectly". All the other error cases would have thrown a different exception.