Unit Testing a Kafka SpringBoot producer - java

I am trying to create a unit test for my Kafka Producer which is integrated into a file. Here's my Kafka Producer:
FileName: MessageProducer.java
public boolean sendMessage(ReceivedMessage message) {
private String topicName = "output-flow";
try{
logger.info("Sending message: {} to topic: {}", message, topicName);
kafkaProducer.send(topicName, message).get();
return true;
} catch (Exception e){
logger.error("Error sending message: {} to topic: {}", message, topicName, e);
return false;
}
}
And here is what I have done so far for my unit test, obviously, with not success at all:
#Mock
private KafkaTemplate<String, ReceivedMessage > kafkaProducer;
private static final String TRANSACTION_TOPIC = "test";
// Function for parameterized values
#ParameterizedTest
#MethodSource("getTransactionProvider")
public void sendMessageTest(ReceivedMessage message) {
MessageProducer mockProducer = new MessageProducer(kafkaProducer);
when(kafkaProducer.send(TRANSACTION_TOPIC, message)).thenReturn({no idea what to put here});
when(mockProducer.sendMessage(message)).thenReturn(true);
assertTrue(mockProducer.sendMessage(message));
}
// Test for exception
// Fails too
#ParameterizedTest
#MethodSource("getTransactionProvider")
public void sendMessageTest_ThrowsException(ReceivedMessage message) {
MessageProducer mockProducer = new MessageProducer(kafkaProducer);
when(kafkaProducer.send(TRANSACTION_TOPIC, message)).thenThrow(new RuntimeException());
assertThrows(RuntimeException.class, () -> mockProducer.sendMessage(null));
}
I get Exception: org.opentest4j.AssertionFailedError: Expected java.lang.RuntimeException to be thrown, but nothing was thrown. for the latter unit test.

If I understood your question, you should return a new SendResult that would have the methods implemented with the data you expect
https://docs.spring.io/spring-kafka/api/org/springframework/kafka/support/SendResult.html
And wrap it in a Future
ListenableFuture<SendResult<K,​V>>
Alternatively, make sendMessage void method (or return a Future itself), and pass in a producer callback parameter that's carried through to send, rather than making it block. Then you can assert the response of the callback

Welcome to SO...
Why does your test case fail?
Because your logic will not throw an error.
Your function will not throw an exception since you catch the exception inside the function as follow and you return boolean value.
catch (Exception e){
logger.error("Error sending message: {} to topic: {}", message, topicName, e);
return false;
}
In that case, you need to test whether the function returns false or not.
As I commented earlier, don't block the main thread by calling the get method in the future object. You can simply implement the future callbacks which can be invoked once you get the result as following
public void sendMessage(ReceivedMessage message) {
private String topicName = "output-flow";
try{
logger.info("Sending message: {} to topic: {}", message, topicName);
ListenableFuture<SendResult<String, String>> future = kafkaProducer.send(topicName, message);
future.addCallback(new ListenableFutureCallback<SendResult<String, String>>() {
#Override
public void onSuccess(SendResult<String, String> result) {
System.out.println("Message Sent " + result.getRecordMetadata().timestamp());
//your logic for the success scenario
}
#Override
public void onFailure(Throwable ex) {
System.out.println(" sending failed ");
// your logic if failed
throw new RuntimeException("Kafka Failed");
}
});
} catch (Exception e){
logger.error("Error sending message: {} to topic: {}", message, topicName, e);
throw new RuntimeException("Exception occurred");
}
}

Related

AWS - SQS deletionPolicy on specific custom exception

In AWS sqs i need to consider message as failed and retry it only on specific custom exception rather than on all runtime exception
#SqsListener(value = "/MyQueueURL", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS )
public void getMessageFromSqs(MyMessage message) {
log.info("message: {}", message);
// Ignore other exceptions
if(somecondition) {
throw new MyCustomException("Retry it"); //<--- Fail only on this exception
}
log.info("Success");
}
Then you need to use java try catch block, to hide the other errors. Although this concept does seem risky as ANY coding error you could lose data.
Since the deletion policy is on success it will automatically remove message from SQS.
#SqsListener(value = "/MyQueueURL", deletionPolicy = SqsMessageDeletionPolicy.ON_SUCCESS )
public void getMessageFromSqs(MyMessage message) {
try {
log.info("message: {}", message);
// Ignore other exceptions
if(somecondition) {
throw new MyCustomException("Retry it"); //<--- Fail only on this exception
}
log.info("Success");
} catch(Exception e) {
log.info("error")
if (e instanceOf MyCustomException) {
throw e
}
}
}

Wait and notify for sending bunch of messages

Would be super grateful if someone can explain me how wait/notify/notifyAll works and if is there better solution for the problem I am facing. Basically, we have to send a bunch of SMS messages. For sending messages an object called SMPPSession is used but in this example I'll just use superficial code. SMPPSession is supposed to send messages to SMSC server and to reestablish session in situations when connection breaks. I would like to use multiple threads for sending multiple messages, and to have a separate single thread, some sort of "guardian"/ "watcher"/"notifier". The role of that separate thread is to stop all other threads from executing their code, while it works on reestablishing session. Naturally, SMPPSession is shared among all these threads. Once that guardian finishes reconnecting, all other thread needs to continue with using the session and proceed with sending.
Now, I have some code and getting exception. Any help?
In reality we do send real SMS messages using jsmpp library and inside it there is SMPPSession object.
public class SMPPSession {
private boolean bind;
private static final Random idGenerator = new Random();
public int sendMessage(String msg){
try{
Thread.sleep(1000L);
System.out.println("Sending message: " + msg);
return Math.abs(idGenerator.nextInt());
} catch (InterruptedException e){
e.printStackTrace();
}
return -1;
}
public void reBind(){
try{
System.out.println("Rebinding...");
Thread.sleep(1000L);
this.bind = true;
System.out.println("Session established!");
} catch (InterruptedException e){
e.printStackTrace();
}
}
public boolean isBind(){
return this.bind;
}
}
public class Sender extends Thread{
private SMPPSession smppSession;
public Sender(String name, SMPPSession smppSession){
this.setName(name);
this.smppSession = smppSession;
}
#Override
public void run(){
while (!Client.messages.isEmpty()){
synchronized (Client.messages){
if (smppSession.isBind()){
final String msg = Client.messages.remove(0);
final int msgId = smppSession.sendMessage(msg);
System.out.println(Thread.currentThread().getName() + " sent msg and received msgId: " + msgId);
Client.messages.notifyAll();
} else {
try {
Client.messages.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
public class SessionProducer extends Thread{
private SMPPSession smppSession;
public SessionProducer(String name, SMPPSession smppSession){
this.setName(name);
this.smppSession = smppSession;
}
#Override
public void run(){
while (!Client.messages.isEmpty()){
synchronized (Client.messages){
if (!smppSession.isBind()){
smppSession.reBind();
System.out.println(Thread.currentThread().getName() + " managed to reestablish SMPP session.");
Client.messages.notifyAll();
} else{
try {
Client.messages.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
public class Client {
public static final List<String> messages = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
//populate messages from db
messages.add("msg1"); messages.add("msg2"); messages.add("msg3"); messages.add("msg4"); messages.add("msg5"); messages.add("msg6");
SMPPSession smppSession = new SMPPSession();
SessionProducer sessionProducer = new SessionProducer("SessionProducer1", smppSession);
Sender sender1 = new Sender("Sender1", smppSession);
Sender sender2 = new Sender("Sender2", smppSession);
Sender sender3 = new Sender("Sender3", smppSession);
Sender sender4 = new Sender("Sender4", smppSession);
sessionProducer.start();
sender1.start();
sender2.start();
sender3.start();
sender4.start();
}
}
Naturally, I get exception and have no idea why. Somehow threads are not in sync.
Rebinding...
Session established!
SessionProducer1 managed to reestablish SMPP session.
Sending message: msg1
Sender4 sent msg and received msgId: 432995458
Sending message: msg2
Sender4 sent msg and received msgId: 113629699
Sending message: msg3
Sender4 sent msg and received msgId: 611735717
Sending message: msg4
Sender4 sent msg and received msgId: 1234995659
Sending message: msg5
Sender4 sent msg and received msgId: 922228968
Sending message: msg6
Sender4 sent msg and received msgId: 2097204472
Exception in thread "Sender2" Exception in thread "Sender1" Exception in thread "Sender3" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at java.base/java.util.concurrent.CopyOnWriteArrayList.elementAt(CopyOnWriteArrayList.java:385)
at java.base/java.util.concurrent.CopyOnWriteArrayList.remove(CopyOnWriteArrayList.java:478)
at demo.Sender.run(Sender.java:20)
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at java.base/java.util.concurrent.CopyOnWriteArrayList.elementAt(CopyOnWriteArrayList.java:385)
at java.base/java.util.concurrent.CopyOnWriteArrayList.remove(CopyOnWriteArrayList.java:478)
at demo.Sender.run(Sender.java:20)
java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at java.base/java.util.concurrent.CopyOnWriteArrayList.elementAt(CopyOnWriteArrayList.java:385)
at java.base/java.util.concurrent.CopyOnWriteArrayList.remove(CopyOnWriteArrayList.java:478)
at demo.Sender.run(Sender.java:20)
Your loops call Client.messages.isEmpty() with no synchronization. I haven't spent the time to really understand what your code does—can't see all of it anyway—but I can guess what's happening.
Maybe the list contains one message.
Four threads all see it as not empty.
Four threads all try enter the synchronized(Client.messages) block.
One-by-one, they get in to the block, see that smppSession.isBind() is true, and try to remove a message from the list.
The first thread to remove a message succeeds, and then each of the other four throws an exception because it tried to remove from an empty list.
Recommend a SMS development library sms-client from China to support Smpp.
<dependency>
<groupId>com.chinamobile.cmos</groupId>
<artifactId>sms-client</artifactId>
<version>0.0.7</version>
</dependency>
public void testsmpp() throws Exception {
SmsClientBuilder builder = new SmsClientBuilder();
SmsClient smsClient = builder.uri("smpp://127.0.0.1:18890?username=test01&password=1qaz2wsx&version=52&window=32&maxchannel=1")
.receiver(new MessageReceiver() {
public void receive(BaseMessage message) {
logger.info("receive : {}",message.toString());
}
}).build();
for (int i = 0; i < 5; i++) {
SubmitSm pdu = new SubmitSm();
pdu.setRegisteredDelivery((byte)1);
pdu.setSourceAddress(new Address((byte)0,(byte)0,"10086"));
pdu.setDestAddress(new Address((byte)0,(byte)0,"13800138000"));
pdu.setSmsMsg(new SmsTextMessage("SmsTextMessage " + i,SmsDcs.getGeneralDataCodingDcs(SmsAlphabet.GSM,SmsMsgClass.CLASS_UNKNOWN)));
try {
smsClient.send(pdu, 1000);
} catch (Exception e) {
logger.info("send ", e);
}
}
Thread.sleep(5000000);
}

CompletableFuture recursive restart on exception from exceptionaly() block

Having a hard time to do a reliable retry of a background task which sends request to let's say mail service in order to get latest emails. Once emails successfully received the execution should continue in thenAccept() block - persist emails, however if exception occurs I have to rerun mail retrieval until successful attempt and on success should persist mails and stop. Please take a look and advice if I do it wrong.
private void retrieveMailsAsync(User user) {
CompletableFuture.supplyAsync(() -> {
try {
return mailService.getEmails(user.getName(), user.getPassword());
} catch (InvalidAuthentication | TimeoutException | BadGatewayException e) {
throw new CompletionException(e);
}
}).thenAccept(email -> {
mailService.persist(email);
}).exceptionally(ex -> {
log.log(Level.SEVERE, "Exception retrieveMailsAsync emails, Retrying retrieveMailsAsync:: ", ex.getCause());
retrieveMailsAsync(user);
return null;
});
}
P.S please also take a look at how I'm handling checked exception wrapping it into CompletionException and rethrowing - the main idea here to handle all exceptions (defined checked and runtime) in one exceptionally() block rather than logging them in catch block and return null.
Thanks guys in advance, hope I'm not doing pretty stupid stuff, or at least there is already reliable solutions exists for Java 8.
What I meant in my comment was this:
private void retrieveMailsAsync(User user) {
CompletableFuture.supplyAsync(() -> {
while (continueQuery()) { // true for infinite retries, or some other logic
try {
return mailService.getEmails(user.getName(), user.getPassword());
} catch (InvalidAuthentication | TimeoutException | BadGatewayException e) {
log.log(Level.SEVERE, "Exception retrieveMailsAsync emails, Retrying retrieveMailsAsync: ", e);
}
}
return null;
}).thenAccept(email -> {
mailService.persist(email);
});
}
Ie you just retry in the submitted runnable until you don't get an exception anymore.
I think you can achieve that via :
public static void main(String[] args) {
String result = call(new User().setName("name").setPassword("p")).join();
System.out.println(result);
}
private static CompletableFuture<String> call(User user) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> retrieveMailsAsync(user));
return future.handleAsync((String result, Throwable ex) -> {
// or any other Predicate that is satisfied against ex
if(ex != null) {
return call(user);
} else {
return future;
}
}).thenCompose(Function.identity());
}
EDIT
So what stays in your way to change the code above, for example, to:
static ExecutorService service = Executors.newFixedThreadPool(1);
public static void main(String[] args) {
call(new User().setName("name").setPassword("p"))
// chain any other action here, like mailService.persist(email);
.thenAcceptAsync(
System.out::println,
service
);
System.out.println("Continue main thread");
}
private static CompletableFuture<String> call(User user) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> retrieveMailsAsync(user), service);
return future.handleAsync((String result, Throwable ex) -> {
// or any other Predicate that is satisfied against ex
if(ex != null) {
return call(user);
} else {
return future;
}
}).thenCompose(Function.identity());
}

Mockito Error in test case while sending message to activeMQ onMessage()

Iam writing an application using ActiveMQ and unit test cases using Mockito where i have a asynchronous onMessage() listener and a method to test the same.The test case fails if i add any if condition in the onMessage().How to avoid this
The code snippet is as below
Main.java
//Initialize boolean here
boolean flag=false;
public void onMessage(final Message message) {
//getting error in the below if condition
if(flag) //Not executing at all Null pointer here
{
if (!(message instanceof TextMessage)) {
//Log error
}
try {
final String messageType = message.getStringProperty("messageType");
_LOG.info("The MessageType is {}", messageType);
final String msg = ((TextMessage) message).getText();
_LOG.debug(msg);
} catch (final JMSException e) {
_LOG.error("We could not read the message", e);
}
}
else //not able to execute if or else conditions
{
//do Something else
}
}
MockTest.java
//Call Main here
#InjectMocks
private Main listener;
#Test
public void shouldProcessMessage() throws JMSException {
final String messageType = "Hello";
final String messageBody ="Hi";
final ActiveMQTextMessage message = new ActiveMQTextMessage();
message.setStringProperty("messageType", messageType);
message.setText(messageBody);
// The below line does not execute at all
// iam getting null pointer exception here
listener.onMessage(message);
}
Has the Boolean value flag been instantiated somewhere in this class?
If you are getting an NPE, it's probably because you have not instantiated the flag variable.

Testing methods using JUnit

I am new to JUnit and I have to test a method using JUnit api. One method internall calls another. My test case goes inside the method but while catchign the exception it fails.
Method under test is
public void checkANDCondition( Map<String, Message> messagesMap ) throws EISClientException
{
List<String> codes = getMessageCodes();
if(isAllReturnedMessagesContainCodes(codes, messagesMap))
{
StringBuffer buff = new StringBuffer("All of the specified message codes matched returned errors.");
for(String code: codes )
{
Message message = messagesMap.get(code);
buff.append(message.getMessageCode() + ": " + message.getMessageType() + ": " + message.getMessageText() + " ");
}
throw new EISClientException(buff.toString());
}
}
public boolean isAllReturnedMessagesContainCodes(List<String> codes, Map<String, Message> messagesMap)
{
if(codes!=null)
{
for(String code: codes)
{
if(!messagesMap.containsKey(code))
{
return false;
}
}
}
return true;
}
What I have done so far is
#Test
public void testPostProcess() throws Exception {
clientResponse = mock(ClientResponse.class);
MessageToExceptionPostProcessFilter postProcessFilter = new MessageToExceptionPostProcessFilter();
RetrieveBillingServiceResponse serviceResponse = new RetrieveBillingServiceResponse();caughtException = false;
try {
postProcessFilter.setCondition(ConditionOperator.AND);
List<String> messagesCodes = new ArrayList<String>();
messagesCodes.add("200");
messagesCodes.add("400");
Message message = new Message();
message.setMessageCode("200");
message.setMessageType(MessageTypeEnum.MESSAGE_TYPE_INFO);
message.setMessageText("Service completed successfully");
serviceResponse.setMessages(Arrays.asList(message));
postProcessFilter.setMessageCodes(messagesCodes);
serviceResponse = postProcessFilter.postProcess(serviceResponse, clientResponse);
assertNotNull(serviceResponse.getMessages());
} catch (EISClientException ex) {
caughtException = true;
assertEquals("All of the specified message codes matched returned errors.", ex.getMessage());
}
assertTrue(caughtException);
}
How can I make it pass?
Thanks
#Test(expected = EISCLientException.class)
public void testPostProcess() throws Exception {
...
serviceResponse.getMessages();
fail("Shouldn't reach this point");
}
That way you don't need to catch, with expected if it does not get throw a EISClientException it will fail.
edit: There are two times I can think of where you wouldn't want to use this.
1) You are mocking exceptions that are thrown mock(exception.class);
this i believe then throws some Mockito excpetion and it will not match the expected exception.
2) You are wrapping caught exceptions in your code, and throwing a generic exception. Example of code:
try {
} catch (FileParseException e){
throw new (ProjectFailingException(e, "file is bad");
}
if you have multiple catches and are wrapping them as ProjectFailingExceptions then you may want to catch in the test like this...
#Test ( expected = FileParseException.class)
public void testProcess() {
try {
...
} catch (ProjectFailingException e){
throw e.getCause();
}
Then the proper exception is thrown and you can make sure that process isn't throwing an exception from a a different catch.

Categories