I have an application that publishes event to RabbitMQ and a consumer which consumes the event. My question is is there a way to write a unit test to test the functionality of this consumer.
Just to add this, the consumer works more in an hierarchical structure i.e, if an order event is posted, the suborders in it are extracted and posts their corresponding events to a queue when the suborders get consumed the lineItems in each one is also posted to a queue and lastly the details for each lineItem would be posted to.
It looks like the way to have an easy-to-use solution for testing RabbitMQ-related develoment is still far to reach.
See this discussion and this discussion from SpringFramework forum. They are either using Mockito (for unit-tests) or a real RabbitMQ instance (integration tests) for their own testing.
Also, see this post where the author uses an real RabbitMQ byt with some facilities to make that more 'test-friendly' task. However, as of now, the solution is valid only for MAC users!
Extending on the previous answer, here's a very quick implementation for the unit test route (using Mockito). The starting point is one of RabbitMQ's own tutorials for java.
Receiver class (message handler)
public class LocalConsumer extends DefaultConsumer {
private Channel channel;
private Logger log;
// manual dependency injection
public LocalConsumer(Channel channel, Logger logger) {
super(channel);
this.log = logger;
}
#Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body, "UTF-8");
// insert here whatever logic is needed, using whatever service you injected - in this case it is a simple logger.
log.print(" [x] Received and processed '" + message + "'");
}
}
Test class
public class LocalConsumerTest {
Logger mockLogger = mock(Logger.class);
Channel mockChannel = mock(Channel.class);
String mockConsumerTag = "mockConsumerTag";
LocalConsumer _sut = new LocalConsumer(mockChannel, mockLogger);
#Test
public void shouldPrintOutTheMessage () throws java.io.IOException {
// arrange
String message = "Test";
// act
_sut.handleDelivery(mockConsumerTag, null, new AMQP.BasicProperties(), message.getBytes() );
// assert
String expected = " [x] Received and processed '" + message + "'";
verify(mockLogger).print(eq(expected));
}
}
Consumer
// ...
// this is where you inject the system you'll mock in the tests.
Consumer consumer = new LocalConsumer(channel, _log);
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, consumer);
// ...
Related
I have been using Spring Kafka for sometime now but only recently have run across a need to "overload" a single Kafka topic. Consider the below code.
#KafkaListener(topics = "my-topic")
public void handleAsString(#Payload #Valid String message) {
...
}
#KafkaListener(topics = "my-topic")
public void handleAsUser(#Payload #Valid User user) {
...
}
Is this best achieved using #KafkaHandler? I have only been successful in both methods executing when the topic is received but the desire is to treat this like a standard overloaded method.
Generally, a golden thumb of rule with Kafka is to use one topic for one type of data stream. So in case you have different types of data coming in through the same stream maybe you'd want to rethink the approach and split the different types of messages to different Kafka Topics and write separate consumers for them.
If you had to do it in one topic, I'd say receive the message as string and then deserialize it based on certain criteria like existence of a key per say.
Assume two messages:
"Hey There!" (String message)
"{"id": 1, "name": \"john\", "age": 26}" (Serialized User Message)
Below is a sample
// maybe make a bean of this
private final ObjectMapper mapper = new ObjectMapper();
#KafkaListener(topics = "my-topic")
public void handleAsUser(#Payload String message) throws IOException {
if (message.contains("age")){
User userMessage = mapper.readValue(message, User.class);
// do something when a user message is received
return;
}
System.out.println("The message is of type String");
}
My Situation
I'm building a small web chat to learn about Spring and Spring WebSocket. You can create different rooms, and each room has it's own channel at /topic/room/{id}.
My goal is to detect when users join and leave a chat room and I thought I could use Spring WebSocket's SessionSubscribeEvent and SessionUnsubscribeEvent for this.
Getting the Destination from the SessionSubscribeEvent is trivial:
#EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
However, the SessionUnsubscribeEvent does not seem to carry the destination channel, destination is null in the following snippet:
#EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
My Question
Is there a better way to watch for subscribe/unsubscribe events and should I even be using those as a way for a user to "log in" to a chat room, or should I rather use a separate channel to send separate "log in"/"log out" messages and work with those?
I thought using subscribe/unsubscribe would've been very convenient, but apparently Spring makes it very hard, so I feel like there has to be a better way.
STOMP Headers only appear in the frames relevant to your question as described here: https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE and here: https://stomp.github.io/stomp-specification-1.2.html#UNSUBSCRIBE
Only the SUBSCRIBE frame has both destination and id, the UNSUBSCRIBE frame has only an id.
This means you have to remember the subscription id with the destination for future lookup. Care must be taken because different Websocket connections usually use/assign the same subscription ids, so to save destinations reliably, you have to include the websocket session id in your storage key.
I wrote the following method to get it:
protected String getWebsocketSessionId(StompHeaderAccessor headerAccessor)
{
// SimpMessageHeaderAccessor.SESSION_ID_HEADER seems to be set in StompSubProtocolHandler.java:261 ("headerAccessor.setSessionId(session.getId());")
return headerAccessor.getHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER).toString();
}
StompHeaderAccessor is created like this:
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionSubscribeEvent)event).getMessage());
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionUnsubscribeEvent)event).getMessage());
This can then be used to create a unique subscription id which can be used as a key for a map to save data about the subscription, including the destination:
protected String getUniqueSubscriptionId(StompHeaderAccessor headerAccessor)
{
return getWebsocketSessionId(headerAccessor)+"--"+headerAccessor.getSubscriptionId();
}
Like this:
Map<String, String> destinationLookupTable=...;
// on subscribe:
destinationLookupTable.put(getUniqueSubscriptionId(headerAccessor), destination);
// on other occasions, including unsubscribe:
destination=destinationLookupTable.get(getUniqueSubscriptionId(headerAccessor));
I think using SessionSubscribeEvent and SessionUnsubscribeEvent is a good idea for that matter. You can get the destination if you keep track of the SessionID:
private Map<String, String> destinationTracker = new HashMap<>();
#EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
destinationTracker.put(headers.getSessionId(), headers.getDestination());
//...
}
#EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
final String destination = destinationTracker.get(headers.getSessionId());
//...
}
I'm studying undertow because I've seen is a good choice if you want to implement Non-Blocking IO and you want to have a reactive http listener.
Undertow uses handlers to handle http requests in a Non-Blocking way.
If I have some logic to be implemented between request and response, how to make this logic to be Non-Blocking too, inside of an undertow handler?
I mean, if it's inserted (or called) within the handleRequest() method is already dispatched to a working thread and then already Non-Blocking or do you need to use CompletableFuture, or Rx Observable or any other reactive library, in order to guarantee that all the stack is reactive?
This is my Handler class as a title of example, I simulate to read a Json which will be parsed into a Person.class object and the transformed (business logic) and then returned back as a Json response.
I've written the two alternatives, in order to understand better how to make the whole stack reactive and Non-Blocking.
Which one do I have to use?
public class DoBusinessLogicHandler implements HttpHandler {
JsonConverter json = JsonConverter.getInstance();
#Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
if (exchange.isInIoThread()) {
exchange.dispatch(this);
return;
}
Pooled<ByteBuffer> pooledByteBuffer = exchange.getConnection().getBufferPool().allocate();
ByteBuffer byteBuffer = pooledByteBuffer.getResource();
byteBuffer.clear();
exchange.getRequestChannel().read(byteBuffer);
int pos = byteBuffer.position();
byteBuffer.rewind();
byte[] bytes = new byte[pos];
byteBuffer.get(bytes);
byteBuffer.clear();
pooledByteBuffer.free();
String requestBody = new String(bytes, Charset.forName("UTF-8") );
/* FIRST ALTERNATIVE:
you can call the business logic directly because the whole body of handleRequest() is managed reactively
*/
Person person = (Person) json.getObjectFromJson(requestBody, Person.class);
Person p = transform(person);
sendResponse(exchange, json.getJsonOf(p));
/* SECOND ALTERNATIVE
you must wrap business logic within a reactive construction (RxJava, CompletableFuture, ecc.) in order to
have all the stack reactive
*/
CompletableFuture
.supplyAsync(()-> (Person) json.getObjectFromJson(requestBody, Person.class))
.thenApply(p -> transform(p))
.thenAccept(p -> sendResponse(exchange, json.getJsonOf(p)));
}
/* it could be also a database fetch or whatever */
private Person transform(Person p){
if(p!=null){
p.setTitle(p.getTitle().toUpperCase());
p.setName(p.getName().toUpperCase());
p.setSurname(p.getSurname().toUpperCase());
}
return p;
}
private void sendResponse(HttpServerExchange exchange, String response){
exchange.getResponseHeaders()
.put(Headers.CONTENT_TYPE, "application/json");
exchange.getResponseSender()
.send(response);
}
}
I am trying to write unit test for one of my actors which is derived from AbstractPersistentActorWithAtLeastOnceDelivery using TestKit. I need to create an actor with TestActorRef.create(...) since I need to get an underlyingActor in order to inject the mocks into actor's implementation.
My (simplified) actor
public class MyActor extends AbstractPersistentActorWithAtLeastOnceDelivery {
#Override
public Receive createReceive() {
return receiveBuilder().match(String.class, message -> {
persist(new MessageSent(message), event -> updateState(event));
}).match(ConfirmMessage.class, confirm -> {
persist(new MessageConfirmed(confirm.deliveryId), event ->
updateState(event));
}).matchAny(message -> log.info("Received unexpected message of class {}.
Content: {}", message.getClass().getName(), message.toString())).build();
}
void updateState(Object received) {
if (received instanceof MessageSent) {
final MessageSent messageSent = (MessageSent) received;
ActorRef destinationActor =
findDestinationActor(messageSent.messageData);
deliver(actorSystem.actorSelection(destinationActor.path()),
deliveryId -> new Message(deliveryId, messageSent.messageData));
} else if (received instanceof MessageConfirmed) {
final MessageConfirmed messageConfirmed = (MessageConfirmed) received;
confirmDelivery(messageConfirmed.deliveryId);
}
}
Unit test:
#Test
public void actorTest() {
ActorSystem system = ActorSystem.create();
TestKit probe = new TestKit(system);
TestActorRef<myActor> testActor = TestActorRef.create(system, props,
probe.getRef());
MyActor myActor = testActor.underlyingActor();
injectMocks(myActor); // my method
testActor.tell("testMessage", probe.getRef());
List<Object> receivedMessages = probe.receiveN(1, FiniteDuration.create(3,
TimeUnit.SECONDS));
}
In debugger I see that deliver() method inside updateState() is called, but the unit test fails with error:
assertion failed: timeout (3 seconds) while expecting 1 messages (got 0)
I am wondering if it is possible at to use the TestKit to test an actor created via TestActorRef and if the fact that my actor extends AbstractPersistentActorWithAtLeastOnceDelivery has something to do with tests failure
It is not possible to use TestActorRef with Akka persistence. It sometimes works but often fails in ways you described in your question. Other features of TestKit work fine with Akka persistence.
See the warning under https://doc.akka.io/docs/akka/current/testing.html#synchronous-testing-testactorref:
Warning
Due to the synchronous nature of TestActorRef it will not work with some support traits that Akka provides as they require asynchronous behaviors to function properly. Examples of traits that do not mix well with test actor refs are PersistentActor and AtLeastOnceDelivery provided by Akka Persistence.
Brief description of my project:
I'm writing a java class named "GreetingsNode", that works in a distributed environment where there is a "managementNode", that is just as service repository and receives and stores info (host port number and service offered) of other nodes and dispatches RPCs of methods offered by services registered. If a node can answer to an RPC, then a thrift socket is opened and a connection is established between the calling node and the answering node, and the answering node returns the result.
I'm using Apache thrift as IDL and framework for RPCs.
Now the problem.
My GreetingsNodeHandler class implements a simple thrift interface containing a single method "getHello(user)" (user being a struct containing the name of the node, which is a parameter of the constructor of GreetingsNode class).
When a GreetingsNode X, connected to the management Node, makes an RPC of that method, another registered GreetingsNode must answer with the message "hello X".
I don't understand properly how to implement the part of the handler where the result is returned, and consequently I fail to understand how I should write the junit test that should check if the method implementation works correctly.
an assert like
assertEquals(client.getHello(user).getMessage(), "Hello John Doe")
would work, but I don't get how, in my case, should i put the client part...
The code for GreetingService thrift service:
struct Message {
1: string message
}
struct User {
1: string name
}
service GreetingsService {
Message getHello(1: User user)
}
Code for GreetingsServiceHandler that must implement GreetingsService method getHello()
public class GreetingsServiceHandler implements GreetingsService.Iface {
private static Random random = new Random(10);
private ManagementService.Client managementClient;
private GreetingsService.Client helloClient;
#Override
public Message getHello(User user) throws TException {
Message answer = null;
// class ServiceProvider is generated by thrift, part of ManagementService thrift service
ServiceProvider provider = null;
List<ServiceProvider>providers = managementClient.getProvidersForService(user.name);
if (providers.isEmpty())
throw new NoProviderAvailableException(); //separate file contains Exception
else {
provider = providers.get(random.nextInt(providers.size()));
//connection between nodes is established here
TTransport helloTransport = new TSocket(provider.getHostName(), provider.getPort());
TProtocol helloProtocol = new TBinaryProtocol(helloTransport);
helloClient = new GreetingsService.Client(helloProtocol);
helloTransport.open();
// here lies my problem
answer = helloClient.getHello(user);
//if I use this instead, then helloClient variable is clearly not used, but of course I need it to answer the method call
answer = answer.setMessage("Ciao " + user.getName() + ", welcome among us!");
}
return answer;
}
and GreetingsNode code is the following:
public class GreetingsNode implements NodeIface {
private ThriftServer helloServer;
private ManagementService.Client managementClient;
private NodeManifest nodeManifest;
private User user;
private String name;
public GreetingsNode(NodeManifest nodeManifest, String name) {
this.nodeManifest = nodeManifest;
this.helloServer = new ThriftServer(GreetingsServiceHandler.class);
this.name = name;
}
#Override
public void turnOn() throws TException {
helloServer.start();
TSocket helloServerTransport = new TSocket("localhost", Constants.SERVER_PORT);
TBinaryProtocol helloServerProtocol = new TBinaryProtocol(helloServerTransport);
managementClient = new ManagementService.Client(helloServerProtocol);
this.setUser(new User(name));
helloServerTransport.open();
helloServer = new ThriftServer(GreetingsServiceHandler.class);
//portNegotiator is a class described in a separate file, that handles the registration of other nodes to the managementNode. NodeManifest is a file generated by thrift, part of managementService thrift file, describing a struct that contains hostname and port number of nodes.
PortNegotiator negotiator = new PortNegotiator(managementClient);
negotiator.negotiate(nodeManifest, helloServer);
}
#Override
public void turnOff() {
helloServer.stop();
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
The basic method impl in the handler is pretty simple, something like the following should do (disclaimer: not tested):
#Override
public Message getHello(User user) throws TException {
Message answer = new Message();
answer = answer.setMessage("Ciao " + user.getName() + ", welcome among us!");
return answer;
}
if I use this instead, then helloClient variable is clearly not used, but of course I need it to answer the method call
When a GreetingsNode X, connected to the management Node, makes an RPC of that method, another registered GreetingsNode must answer with the message "hello X".
If that means that we want a call sequence like Client => ServerA => Server B then this is also possible and requires only slight modifications. Starting from our basic example above, we enhance the code accordingly:
private Message callTheOtherNode(User user) {
// class ServiceProvider is generated by Thrift,
// part of ManagementService Thrift service
ServiceProvider provider = null;
List<ServiceProvider>providers = managementClient.getProvidersForService(user.name);
if (providers.isEmpty())
throw new NoProviderAvailableException(); //separate file contains Exception
provider = providers.get(random.nextInt(providers.size()));
//connection between nodes is established here
TTransport helloTransport = new TSocket(provider.getHostName(), provider.getPort());
TProtocol helloProtocol = new TBinaryProtocol(helloTransport);
helloClient = new GreetingsService.Client(helloProtocol);
helloTransport.open();
return helloClient.getHello(user);
}
#Override
public Message getHello(User user) throws TException {
Message answer = callTheOtherNode(user);
return answer;
}
Of course the "other node" being called needs to actually do something with the request, instead of simply forwarding it again to yet another node.