12

I have the following method in my Java class:

public class AwsHelper {

  private AmazonSQS sqs;

  private void sendMessageToQueue(String message){

        sqs = AmazonSQSClientBuilder.defaultClient();

        SendMessageRequest sendMessageRequest = new SendMessageRequest();
        sendMessageRequest.setQueueUrl("");
        sendMessageRequest.setMessageBody(message);
        sendMessageRequest.setMessageGroupId("");

        sqs.sendMessage(sendMessageRequest);
}

I want to be able to mock the behavior of sqs.sendMessage(sendMessageRequest);so that my unit test does not send a message to a queue.

I have tried to do so as follows in my test class, but sqs actually sends a message to the queue when my test is executed. Assume this is due to being assigned by AmazonSQSClientBuilder.defaultClient().

How can I solve this?

public class AwsSQSReferralsUtilTest {
    
        @Spy
        @InjectMocks
        private AwsHelper awsHelper;
    
        @Mock
        AmazonSQS sqs;
    
        @BeforeClass
        public void setUp() {
            MockitoAnnotations.initMocks(this);
        }
    
        @AfterMethod
        public void afterEachMethod() {
            Mockito.reset(awsHelper);
        }
    
        @Test
        public void shouldSendMessage() {
    
            Mockito.when((sqs.sendMessage(any(SendMessageRequest.class)))).thenReturn(new SendMessageResult());
    
            awsHelper.sendMessageToQueue("");
        }
}
Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35
java12399900
  • 1,485
  • 7
  • 26
  • 56
  • But why not use IoC or Dependency Injection concept here? I have many services like this and the way I do it is, I will pass the `SqsClient` as a dependency to the `AwsHelper` class. That way on your unit tests you can just pass simple mock as dependency. Let me know if this makes sense to you, I can post as answer with example. – dsharew Feb 02 '21 at 13:06

3 Answers3

6

I recommend to use approach from article: https://github.com/mockito/mockito/wiki/Mocking-Object-Creation

You need to change little bit a class, applying approach for mocking from article in following way:

AwsHelper

public class AwsHelper {

    private AmazonSQS sqs;

    public void sendMessageToQueue(String message) {
        sqs = defaultClient();

        SendMessageRequest sendMessageRequest = new SendMessageRequest();
        sendMessageRequest.setQueueUrl("");
        sendMessageRequest.setMessageBody(message);
        sendMessageRequest.setMessageGroupId("");

        sqs.sendMessage(sendMessageRequest);
    }

    protected AmazonSQS defaultClient() {
        return AmazonSQSClientBuilder.defaultClient();
    }
}

AwsSQSReferralsUtilTest

public class AwsSQSReferralsUtilTest {

    @Spy
    private AwsHelper awsHelper;

    @Mock
    private AmazonSQS sqs;

    @BeforeClass
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }
    
    @AfterMethod
    public void afterEachMethod() {
        Mockito.reset(awsHelper);
    }

    @Test
    public void shouldSendMessage() {
        //mocking object creation
        doReturn(sqs).when(awsHelper).defaultClient();

        when(sqs.sendMessage(any(SendMessageRequest.class))).thenReturn(new SendMessageRequest());
        awsHelper.sendMessageToQueue("");
    }

}
saver
  • 2,541
  • 1
  • 9
  • 14
1

Here is an example of using MOCK to test SQS API (sendMessage). It passed and did not send the queue an actual message. When you use MOCK, you are not invoking real AWS endpoints. Its just testing the API - not modifying AWS resources.

enter image description here

Code for SQS Mock

import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.io.*;
import java.util.*;
import static org.mockito.Mockito.mock;

@TestInstance(TestInstance.Lifecycle.PER_METHOD)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SQSServiceMock {

    private static SqsClient sqsClient;

    private static String queueName ="";
    private static String queueUrl ="" ; // set dynamically in the test
    private static String message ="";
    private static String dlqueueName ="";
    private static List<Message> messages = null; // set dynamically in the test


    @BeforeAll
    public static void setUp() throws IOException {

        try {
            sqsClient = mock(SqsClient.class);

        } catch (Exception ex) {
            ex.printStackTrace();
        }


        try (InputStream input = SQSServiceIntegrationTest.class.getClassLoader().getResourceAsStream("config.properties")) {

            Properties prop = new Properties();

            if (input == null) {
                System.out.println("Sorry, unable to find config.properties");
                return;
            }

            //load a properties file from class path, inside static method
            prop.load(input);

            // Populate the data members required for all tests
            queueName = prop.getProperty("QueueName");
            message = prop.getProperty("Message");
            dlqueueName=prop.getProperty("DLQueueName");


        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    @Test
    @Order(1)
    public void whenInitializingAWSS3Service_thenNotNull() {
        assertNotNull(sqsClient);
        System.out.println("Test 1 passed");
    }

    @Test
    @Order(2)
    public void SendMessage() {

        SendMessageRequest sendMsgRequest = SendMessageRequest.builder()
                .queueUrl("https://sqs.us-east-1.amazonaws.com/000000047983/VideoQueue")
                .messageBody(message)
                .delaySeconds(5)
                .build();

        sqsClient.sendMessage(sendMsgRequest);
        System.out.println("Test 2 passed");
    }
}
smac2020
  • 9,637
  • 4
  • 24
  • 38
0

Mocking static methods with Mockito is possible, using PowerMock.

The following test works:

import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClientBuilder;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SendMessageResult;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.*;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.mockito.ArgumentMatchers.any;

@RunWith(PowerMockRunner.class)
@PrepareForTest(AmazonSQSClientBuilder.class)
public class AwsSQSReferralsUtilTest {

    private AwsHelper awsHelper = new AwsHelper();

    @Mock
    AmazonSQS sqs;

    @BeforeClass
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @AfterMethod
    public void afterEachMethod() {
        Mockito.reset(awsHelper);
    }

    @Test
    public void shouldSendMessage() {

        PowerMockito.mockStatic(AmazonSQSClientBuilder.class);
        BDDMockito.given(AmazonSQSClientBuilder.defaultClient()).willReturn(sqs);


        Mockito.when((sqs.sendMessage(any(SendMessageRequest.class)))).thenReturn(new SendMessageResult());

        awsHelper.sendMessageToQueue("");
    }
}

Without changing any of your source code. I am not sure you can use this, because this solution uses junit, while you tagged the question with testng.

While trying to run with testng I got this problem:

powermock not prepared for test

So I solved it using the post Mockito asks to add @PrepareForTest for the class even after adding @PrepareForTest.

Tomer Shetah
  • 8,413
  • 7
  • 27
  • 35