How to Mock Amazon SQS in Unit Tests with Dependency Injection and Mockito
These articles are AI-generated summaries. Please check the original sources for full details.
How to Mock Amazon SQS in Unit Tests with Dependency Injection and Mockito
This article explains how to mock Amazon SQS (Simple Queue Service) in unit tests using dependency injection and Mockito. The approach ensures tests are fast, deterministic, and do not require real AWS credentials, while verifying that code constructs the correct requests without interacting with actual SQS endpoints.
Key Concepts and Implementation
1. Why Mock Amazon SQS?
- Purpose: Avoid network dependencies, reduce test execution time, and ensure consistent test results.
- Impact: Eliminates reliance on real AWS infrastructure, making tests more reliable and faster.
- Benefits:
- No need for AWS credentials or internet connectivity.
- Enables focused testing of business logic without external service interactions.
2. Dependencies
- AWS SDK for Java: Required for interacting with SQS.
- Version used:
2.35.10 - Provides classes like
SqsClient,SqsAsyncClient, and request/response models.
- Version used:
- Testing Libraries:
- JUnit 5: For writing and running tests.
- Mockito: For creating mock objects and stubbing behavior.
- AssertJ: For assertions in tests.
3. Service Under Test
- Class:
SqsMessagePublisher- Responsibility: Sends messages to an SQS queue.
- Design: Uses dependency injection to accept an
SqsClientvia constructor. - Code Example:
public class SqsMessagePublisher { private final SqsClient sqsClient; public SqsMessagePublisher(SqsClient sqsClient) { this.sqsClient = sqsClient; } public String publishMessage(String queueUrl, String messageBody) { SendMessageRequest request = SendMessageRequest.builder() .queueUrl(queueUrl) .messageBody(messageBody) .build(); SendMessageResponse response = sqsClient.sendMessage(request); return response.messageId(); } } - Key Design Principle: Decouples business logic from the concrete SQS client implementation, enabling easy substitution with a mock in tests.
4. Mocking Synchronous SqsClient
- Test Setup:
- Use
@Mockto create a mockSqsClient. - Use
@InjectMocksto inject the mock into the service under test. - Example:
@ExtendWith(MockitoExtension.class) class SqsMessagePublisherUnitTest { @Mock private SqsClient sqsClient; @InjectMocks private SqsMessagePublisher messagePublisher; }
- Use
- Test Implementation:
- Arrange:
- Stub the
sendMessagemethod to return a predefined response. - Example:
when(sqsClient.sendMessage(any(SendMessageRequest.class))) .thenReturn(mockResponse);
- Stub the
- Act:
- Call
publishMessageto trigger the method under test.
- Call
- Assert:
- Use
ArgumentCaptorto verify theSendMessageRequestparameters. - Example:
ArgumentCaptor<SendMessageRequest> requestCaptor = ArgumentCaptor.forClass(SendMessageRequest.class); verify(sqsClient).sendMessage(requestCaptor.capture()); SendMessageRequest capturedRequest = requestCaptor.getValue(); assertThat(capturedRequest.queueUrl()).isEqualTo(queueUrl); assertThat(capturedRequest.messageBody()).isEqualTo(messageBody);
- Use
- Arrange:
5. Mocking Asynchronous SqsAsyncClient
- Service Class:
SqsAsyncMessagePublisher- Uses
SqsAsyncClientand returns aCompletableFuture<String>.
- Uses
- Test Adjustments:
- Stub the
sendMessagemethod to return aCompletableFuturewrapped around a mock response. - Example:
when(sqsAsyncClient.sendMessage(any(SendMessageRequest.class))) .thenReturn(CompletableFuture.completedFuture(mockResponse)); - Use
.get()on theCompletableFutureto retrieve the result synchronously during assertions.
- Stub the
6. Integration Testing Considerations
- Limitation of Unit Tests: Cannot validate IAM permissions, network connectivity, or real queue configurations.
- Solution: Use Testcontainers with LocalStack to emulate AWS services locally.
- Allows testing against a real SQS endpoint without actual AWS credentials.
7. Conclusion
- Summary: Mocking Amazon SQS with Mockito and dependency injection enables fast, reliable unit tests that focus on business logic.
- Best Practices:
- Always use dependency injection for external services.
- Validate request parameters using
ArgumentCaptor. - Use
CompletableFuturefor async clients.
- Real-World Use Cases:
- Testing message publishing logic in microservices.
- Validating error handling for malformed requests.
- Ensuring queue URLs and message bodies are correctly formatted.
Working Example
Synchronous Test Example
@Test
void whenPublishMessage_thenMessageIsSentWithCorrectParameters() {
// Arrange
String queueUrl = "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue";
String messageBody = "Hello, SQS!";
String expectedMessageId = "test-message-id-123";
SendMessageResponse mockResponse = SendMessageResponse.builder()
.messageId(expectedMessageId)
.build();
when(sqsClient.sendMessage(any(SendMessageRequest.class))).thenReturn(mockResponse);
// Act
String actualMessageId = messagePublisher.publishMessage(queueUrl, messageBody);
// Assert
assertThat(actualMessageId).isEqualTo(expectedMessageId);
ArgumentCaptor<SendMessageRequest> requestCaptor =
ArgumentCaptor.forClass(SendMessageRequest.class);
verify(sqsClient).sendMessage(requestCaptor.capture());
SendMessageRequest capturedRequest = requestCaptor.getValue();
assertThat(capturedRequest.queueUrl()).isEqualTo(queueUrl);
assertThat(capturedRequest.messageBody()).isEqualTo(messageBody);
}
Recommendations
- When to Use This Approach:
- When testing code that interacts with external services like SQS.
- When you need to isolate business logic from infrastructure dependencies.
- Best Practices:
- Always mock external services to avoid network latency.
- Use
ArgumentCaptorto inspect request parameters. - For async clients, ensure
CompletableFutureis properly stubbed.
- Pitfalls to Avoid:
- Forgetting to mock dependencies, leading to real service calls.
- Not verifying request parameters, resulting in incomplete test coverage.
- Using hardcoded values instead of dynamic mocking for flexibility.
Continue reading
Next article
The Ideal Micro-Frontends Platform
Related Content
Introduction to Testing Micronaut With JUnit 5
Learn how to set up and write JUnit tests in a Micronaut application, achieving simplified application context management and dependency injection.
Testing That I Actually Run: A Small Pyramid
A developer outlines a testing strategy prioritizing ultra-fast unit tests with Vitest and a rigorous manual 'Smoke Protocol,' achieving 95% confidence with 10% of the maintenance cost of full E2E suites.
Why Mocking Java Collections with Mockito is Problematic
Mocking Java collections can lead to brittle tests and unrealistic behavior, with potential test failures on newer Java versions.