Initial commit
This commit is contained in:
@@ -0,0 +1,407 @@
|
||||
# Testing Strategies for DynamoDB
|
||||
|
||||
This document provides comprehensive testing strategies for DynamoDB applications using the AWS SDK for Java 2.x.
|
||||
|
||||
## Unit Testing with Mocks
|
||||
|
||||
### Mocking DynamoDbClient
|
||||
```java
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CustomerServiceTest {
|
||||
|
||||
@Mock
|
||||
private DynamoDbClient dynamoDbClient;
|
||||
|
||||
@Mock
|
||||
private DynamoDbEnhancedClient enhancedClient;
|
||||
|
||||
@Mock
|
||||
private DynamoDbTable<Customer> customerTable;
|
||||
|
||||
@InjectMocks
|
||||
private CustomerService customerService;
|
||||
|
||||
@Test
|
||||
void saveCustomer_ShouldReturnSavedCustomer() {
|
||||
// Arrange
|
||||
Customer customer = new Customer("123", "John Doe", "john@example.com");
|
||||
|
||||
when(enhancedClient.table(anyString(), any(TableSchema.class)))
|
||||
.thenReturn(customerTable);
|
||||
|
||||
when(customerTable.putItem(customer))
|
||||
.thenReturn(null);
|
||||
|
||||
// Act
|
||||
Customer result = customerService.saveCustomer(customer);
|
||||
|
||||
// Assert
|
||||
assertNotNull(result);
|
||||
assertEquals("123", result.getCustomerId());
|
||||
verify(customerTable).putItem(customer);
|
||||
}
|
||||
|
||||
@Test
|
||||
void getCustomer_NotFound_ShouldReturnEmpty() {
|
||||
// Arrange
|
||||
when(enhancedClient.table(anyString(), any(TableSchema.class)))
|
||||
.thenReturn(customerTable);
|
||||
|
||||
when(customerTable.getItem(any(Key.class)))
|
||||
.thenReturn(null);
|
||||
|
||||
// Act
|
||||
Optional<Customer> result = customerService.getCustomer("123");
|
||||
|
||||
// Assert
|
||||
assertFalse(result.isPresent());
|
||||
verify(customerTable).getItem(any(Key.class));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Query Operations
|
||||
```java
|
||||
@Test
|
||||
void queryCustomersByStatus_ShouldReturnMatchingCustomers() {
|
||||
// Arrange
|
||||
List<Customer> mockCustomers = List.of(
|
||||
new Customer("1", "Alice", "alice@example.com"),
|
||||
new Customer("2", "Bob", "bob@example.com")
|
||||
);
|
||||
|
||||
DynamoDbTable<Customer> mockTable = mock(DynamoDbTable.class);
|
||||
DynamoDbIndex<Customer> mockIndex = mock(DynamoDbIndex.class);
|
||||
|
||||
QueryEnhancedRequest queryRequest = QueryEnhancedRequest.builder()
|
||||
.queryConditional(QueryConditional.keyEqualTo(Key.builder()
|
||||
.partitionValue("ACTIVE")
|
||||
.build()))
|
||||
.build();
|
||||
|
||||
when(enhancedClient.table("Customers", TableSchema.fromBean(Customer.class)))
|
||||
.thenReturn(mockTable);
|
||||
when(mockTable.index("status-index"))
|
||||
.thenReturn(mockIndex);
|
||||
when(mockIndex.query(queryRequest))
|
||||
.thenReturn(PaginatedQueryIterable.from(mock(Customer.class), mock(QueryResponseEnhanced.class)));
|
||||
|
||||
QueryResponseEnhanced mockResponse = mock(QueryResponseEnhanced.class);
|
||||
when(mockResponse.items())
|
||||
.thenReturn(mockCustomers.stream());
|
||||
|
||||
when(mockIndex.query(any(QueryEnhancedRequest.class)))
|
||||
.thenReturn(PaginatedQueryIterable.from(mock(Customer.class), mockResponse));
|
||||
|
||||
// Act
|
||||
List<Customer> result = customerService.findByStatus("ACTIVE");
|
||||
|
||||
// Assert
|
||||
assertEquals(2, result.size());
|
||||
verify(mockIndex).query(any(QueryEnhancedRequest.class));
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Testing with Testcontainers
|
||||
|
||||
### LocalStack Setup
|
||||
```java
|
||||
@Testcontainers
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class DynamoDbIntegrationTest {
|
||||
|
||||
@Container
|
||||
static LocalStackContainer localstack = new LocalStackContainer(
|
||||
DockerImageName.parse("localstack/localstack:3.0"))
|
||||
.withServices(LocalStackContainer.Service.DYNAMODB);
|
||||
|
||||
@DynamicPropertySource
|
||||
static void configureProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("aws.region", () -> Region.US_EAST_1.toString());
|
||||
registry.add("aws.accessKeyId", () -> localstack.getAccessKey());
|
||||
registry.add("aws.secretAccessKey", () -> localstack.getSecretKey());
|
||||
registry.add("aws.endpoint",
|
||||
() -> localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private DynamoDbEnhancedClient enhancedClient;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
createTestTable();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testCustomerCRUDOperations() {
|
||||
// Test create
|
||||
Customer customer = new Customer("test-123", "Test User", "test@example.com");
|
||||
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
|
||||
.putItem(customer);
|
||||
|
||||
// Test read
|
||||
Customer retrieved = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
|
||||
.getItem(Key.builder().partitionValue("test-123").build());
|
||||
|
||||
assertNotNull(retrieved);
|
||||
assertEquals("Test User", retrieved.getName());
|
||||
|
||||
// Test update
|
||||
customer.setPoints(1000);
|
||||
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
|
||||
.putItem(customer);
|
||||
|
||||
// Test delete
|
||||
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
|
||||
.deleteItem(Key.builder().partitionValue("test-123").build());
|
||||
}
|
||||
|
||||
private void createTestTable() {
|
||||
DynamoDbClient client = DynamoDbClient.builder()
|
||||
.region(Region.US_EAST_1)
|
||||
.endpointOverride(localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB))
|
||||
.credentialsProvider(StaticCredentialsProvider.create(
|
||||
AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())))
|
||||
.build();
|
||||
|
||||
CreateTableRequest request = CreateTableRequest.builder()
|
||||
.tableName("Customers")
|
||||
.keySchema(KeySchemaElement.builder()
|
||||
.attributeName("customerId")
|
||||
.keyType(KeyType.HASH)
|
||||
.build())
|
||||
.attributeDefinitions(AttributeDefinition.builder()
|
||||
.attributeName("customerId")
|
||||
.attributeType(ScalarAttributeType.S)
|
||||
.build())
|
||||
.provisionedThroughput(ProvisionedThroughput.builder()
|
||||
.readCapacityUnits(5L)
|
||||
.writeCapacityUnits(5L)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
client.createTable(request);
|
||||
waiterForTableActive(client, "Customers");
|
||||
}
|
||||
|
||||
private void waiterForTableActive(DynamoDbClient client, String tableName) {
|
||||
Waiter waiter = client.waiter();
|
||||
CreateTableResponse response = client.createTable(request);
|
||||
|
||||
waiter.waitUntilTableExists(r -> r
|
||||
.tableName(tableName)
|
||||
.maxWait(Duration.ofSeconds(30)));
|
||||
|
||||
try {
|
||||
waiter.waitUntilTableExists(r -> r.tableName(tableName));
|
||||
} catch (WaiterTimeoutException e) {
|
||||
throw new RuntimeException("Table creation timed out", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Testcontainers with H2 Migration
|
||||
```java
|
||||
@SpringBootTest
|
||||
@Testcontainers
|
||||
@AutoConfigureDataJpa
|
||||
class CustomerRepositoryTest {
|
||||
|
||||
@Container
|
||||
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
|
||||
.withDatabaseName("testdb")
|
||||
.withUsername("test")
|
||||
.withPassword("test");
|
||||
|
||||
@DynamicPropertySource
|
||||
static void postgresProperties(DynamicPropertyRegistry registry) {
|
||||
registry.add("spring.datasource.url", postgres::getJdbcUrl);
|
||||
registry.add("spring.datasource.username", postgres::getUsername);
|
||||
registry.add("spring.datasource.password", postgres::getPassword);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private CustomerRepository customerRepository;
|
||||
|
||||
@Autowired
|
||||
private DynamoDbEnhancedClient dynamoDbClient;
|
||||
|
||||
@Test
|
||||
void testRepositoryWithRealDatabase() {
|
||||
// Test with real database
|
||||
Customer customer = new Customer("123", "Test User", "test@example.com");
|
||||
customerRepository.save(customer);
|
||||
|
||||
Customer retrieved = customerRepository.findById("123").orElse(null);
|
||||
assertNotNull(retrieved);
|
||||
assertEquals("Test User", retrieved.getName());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Load Testing with Gatling
|
||||
```java
|
||||
class CustomerSimulation extends Simulation {
|
||||
HttpProtocolBuilder httpProtocolBuilder = http
|
||||
.baseUrl("http://localhost:8080")
|
||||
.acceptHeader("application/json");
|
||||
|
||||
ScenarioBuilder scn = scenario("Customer Operations")
|
||||
.exec(http("create_customer")
|
||||
.post("/api/customers")
|
||||
.body(StringBody(
|
||||
"""{
|
||||
"customerId": "test-123",
|
||||
"name": "Test User",
|
||||
"email": "test@example.com"
|
||||
}"""))
|
||||
.asJson()
|
||||
.check(status().is(201)))
|
||||
.exec(http("get_customer")
|
||||
.get("/api/customers/test-123")
|
||||
.check(status().is(200)));
|
||||
|
||||
{
|
||||
setUp(
|
||||
scn.injectOpen(
|
||||
rampUsersPerSec(10).to(100).during(60),
|
||||
constantUsersPerSec(100).during(120)
|
||||
)
|
||||
).protocols(httpProtocolBuilder);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Microbenchmark Testing
|
||||
```java
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.MILLISECONDS)
|
||||
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
|
||||
@Fork(1)
|
||||
@State(Scope.Benchmark)
|
||||
public class DynamoDbPerformanceBenchmark {
|
||||
|
||||
private DynamoDbEnhancedClient enhancedClient;
|
||||
private DynamoDbTable<Customer> customerTable;
|
||||
private Customer testCustomer;
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
enhancedClient = DynamoDbEnhancedClient.builder()
|
||||
.dynamoDbClient(DynamoDbClient.builder().build())
|
||||
.build();
|
||||
|
||||
customerTable = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class));
|
||||
testCustomer = new Customer("benchmark-123", "Benchmark User", "benchmark@example.com");
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void testPutItem() {
|
||||
customerTable.putItem(testCustomer);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void testGetItem() {
|
||||
customerTable.getItem(Key.builder().partitionValue("benchmark-123").build());
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public void testQuery() {
|
||||
customerTable.scan().items().stream().collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Property-Based Testing
|
||||
|
||||
### Using jqwik
|
||||
```java
|
||||
@Property
|
||||
@Report(Reporting.GENERATED)
|
||||
void customerSerializationShouldBeConsistent(
|
||||
@ForAll("customers") Customer customer
|
||||
) {
|
||||
// When
|
||||
String serialized = serializeCustomer(customer);
|
||||
Customer deserialized = deserializeCustomer(serialized);
|
||||
|
||||
// Then
|
||||
assertEquals(customer.getCustomerId(), deserialized.getCustomerId());
|
||||
assertEquals(customer.getName(), deserialized.getName());
|
||||
assertEquals(customer.getEmail(), deserialized.getEmail());
|
||||
}
|
||||
|
||||
@Provide
|
||||
Arbitrary<Customer> customers() {
|
||||
return Arbitraries.one(
|
||||
Arbitraries.of("customer-", "user-", "client-").string()
|
||||
).map(id -> new Customer(
|
||||
id + Arbitraries.integers().between(1000, 9999).sample(),
|
||||
Arbitraries.strings().ofLength(10).sample(),
|
||||
Arbitraries.strings().email().sample()
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Test Data Factory
|
||||
```java
|
||||
@Component
|
||||
public class TestDataFactory {
|
||||
|
||||
private final DynamoDbEnhancedClient enhancedClient;
|
||||
|
||||
@Autowired
|
||||
public TestDataFactory(DynamoDbEnhancedClient enhancedClient) {
|
||||
this.enhancedClient = enhancedClient;
|
||||
}
|
||||
|
||||
public Customer createTestCustomer(String id) {
|
||||
Customer customer = new Customer(
|
||||
id != null ? id : UUID.randomUUID().toString(),
|
||||
"Test User",
|
||||
"test@example.com"
|
||||
);
|
||||
customer.setPoints(1000);
|
||||
customer.setCreatedAt(LocalDateTime.now());
|
||||
|
||||
enhancedClient.table("Customers", TableSchema.fromBean(Customer.class))
|
||||
.putItem(customer);
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
public void cleanupTestData() {
|
||||
// Implementation to clean up test data
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Test Database Configuration
|
||||
```java
|
||||
@TestConfiguration
|
||||
public class TestDataConfig {
|
||||
|
||||
@Bean
|
||||
public TestDataCleaner testDataCleaner() {
|
||||
return new TestDataCleaner();
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public class TestDataCleaner {
|
||||
|
||||
private final DynamoDbClient dynamoDbClient;
|
||||
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void cleanup() {
|
||||
// Clean up test data before each test run
|
||||
}
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user