Pruebas unitarias con aws-smithy-mocks en AWS SDK para Rust
AWS SDK para Rust proporciona varios enfoques para probar el código que interactúa con Servicios de AWS. En este tema se describe cómo utilizar la caja aws-smithy-mocks
Descripción general
Al escribir pruebas para el código que utiliza Servicios de AWS, a menudo se desea evitar realizar llamadas de red reales. La caja aws-smithy-mocks proporciona una solución al permitirle:
-
Crear reglas simuladas que definan cómo debe responder el SDK a solicitudes específicas.
-
Devolver diferentes tipos de respuestas (éxitos, errores, respuestas HTTP).
-
Hacer coincidir las solicitudes en función de sus propiedades.
-
Definir secuencias de respuestas para probar el comportamiento de los reintentos.
-
Verificar que las reglas se hayan utilizado según lo previsto.
Incorporación de la dependencia
En una línea de comandos del directorio del proyecto, agregue la caja aws-smithy-mocks
$cargo add --dev aws-smithy-mocks
Al usar la opción--dev, se agrega la caja a la sección [dev-dependencies] del archivo Cargo.toml. Como dependencia de desarrollo
Este código de ejemplo también usa Amazon Simple Storage Service como Servicio de AWS de ejemplo y requiere la característica test-util.
$cargo add aws-sdk-s3 --features test-util
De esta forma, se agrega la caja a la sección [dependencies] del archivo Cargo.toml.
Uso básico
A continuación, se muestra un ejemplo sencillo de cómo usar aws-smithy-mocks para probar código que interactúa con Amazon Simple Storage Service (Amazon S3):
use aws_sdk_s3::operation::get_object::GetObjectOutput; use aws_sdk_s3::primitives::ByteStream; use aws_smithy_mocks::{mock, mock_client}; #[tokio::test] async fn test_s3_get_object() { // Create a rule that returns a successful response let get_object_rule = mock!(aws_sdk_s3::Client::get_object) .then_output(|| { GetObjectOutput::builder() .body(ByteStream::from_static(b"test-content")) .build() }); // Create a mocked client with the rule let s3 = mock_client!(aws_sdk_s3, [&get_object_rule]); // Use the client as you would normally let result = s3 .get_object() .bucket("test-bucket") .key("test-key") .send() .await .expect("success response"); // Verify the response let data = result.body.collect().await.expect("successful read").to_vec(); assert_eq!(data, b"test-content"); // Verify the rule was used assert_eq!(get_object_rule.num_calls(), 1); }
Creación de reglas simuladas
Las reglas se crean mediante la macro mock!, que toma una operación de cliente como argumento. A continuación, puede configurar el comportamiento de la regla.
Coincidencia de solicitudes
Puede hacer que las reglas sean más específicas haciendo coincidir las propiedades de las solicitudes:
let rule = mock!(Client::get_object) .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("test-key")) .then_output(|| { GetObjectOutput::builder() .body(ByteStream::from_static(b"test-content")) .build() });
Tipos de respuestas diferentes
Puede devolver diferentes tipos de respuestas:
// Return a successful response let success_rule = mock!(Client::get_object) .then_output(|| GetObjectOutput::builder().build()); // Return an error let error_rule = mock!(Client::get_object) .then_error(|| GetObjectError::NoSuchKey(NoSuchKey::builder().build())); // Return a specific HTTP response let http_rule = mock!(Client::get_object) .then_http_response(|| { HttpResponse::new( StatusCode::try_from(503).unwrap(), SdkBody::from("service unavailable") ) });
Prueba del comportamiento de los reintentos
Una de las características más potentes de aws-smithy-mocks es la capacidad de probar el comportamiento de los reintentos mediante la definición de secuencias de respuestas:
// Create a rule that returns 503 twice, then succeeds let retry_rule = mock!(aws_sdk_s3::Client::get_object) .sequence() .http_status(503, None) // First call returns 503 .http_status(503, None) // Second call returns 503 .output(|| GetObjectOutput::builder().build()) // Third call succeeds .build(); // With repetition using times() let retry_rule = mock!(Client::get_object) .sequence() .http_status(503, None) .times(2) // First two calls return 503 .output(|| GetObjectOutput::builder().build()) // Third call succeeds .build();
Modos de reglas
Puede controlar cómo coinciden y se aplican las reglas mediante los modos de regla:
// Sequential mode: Rules are tried in order, and when a rule is exhausted, the next rule is used let client = mock_client!(aws_sdk_s3, RuleMode::Sequential, [&rule1, &rule2]); // MatchAny mode: The first matching rule is used, regardless of order let client = mock_client!(aws_sdk_s3, RuleMode::MatchAny, [&rule1, &rule2]);
Ejemplo: prueba del comportamiento de los reintentos
Este es un ejemplo más completo que muestra cómo probar el comportamiento de los reintentos:
use aws_sdk_s3::operation::get_object::GetObjectOutput; use aws_sdk_s3::config::RetryConfig; use aws_sdk_s3::primitives::ByteStream; use aws_smithy_mocks::{mock, mock_client, RuleMode}; #[tokio::test] async fn test_retry_behavior() { // Create a rule that returns 503 twice, then succeeds let retry_rule = mock!(aws_sdk_s3::Client::get_object) .sequence() .http_status(503, None) .times(2) .output(|| GetObjectOutput::builder() .body(ByteStream::from_static(b"success")) .build()) .build(); // Create a mocked client with the rule and custom retry configuration let s3 = mock_client!( aws_sdk_s3, RuleMode::Sequential, [&retry_rule], |client_builder| { client_builder.retry_config(RetryConfig::standard().with_max_attempts(3)) } ); // This should succeed after two retries let result = s3 .get_object() .bucket("test-bucket") .key("test-key") .send() .await .expect("success after retries"); // Verify the response let data = result.body.collect().await.expect("successful read").to_vec(); assert_eq!(data, b"success"); // Verify all responses were used assert_eq!(retry_rule.num_calls(), 3); }
Ejemplo: diferentes respuestas según los parámetros de solicitud
También puede crear reglas que devuelvan diferentes respuestas en función de los parámetros de solicitud:
use aws_sdk_s3::operation::get_object::{GetObjectOutput, GetObjectError}; use aws_sdk_s3::types::error::NoSuchKey; use aws_sdk_s3::Client; use aws_sdk_s3::primitives::ByteStream; use aws_smithy_mocks::{mock, mock_client, RuleMode}; #[tokio::test] async fn test_different_responses() { // Create rules for different request parameters let exists_rule = mock!(Client::get_object) .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("exists")) .sequence() .output(|| GetObjectOutput::builder() .body(ByteStream::from_static(b"found")) .build()) .build(); let not_exists_rule = mock!(Client::get_object) .match_requests(|req| req.bucket() == Some("test-bucket") && req.key() == Some("not-exists")) .sequence() .error(|| GetObjectError::NoSuchKey(NoSuchKey::builder().build())) .build(); // Create a mocked client with the rules in MatchAny mode let s3 = mock_client!(aws_sdk_s3, RuleMode::MatchAny, [&exists_rule, ¬_exists_rule]); // Test the "exists" case let result1 = s3 .get_object() .bucket("test-bucket") .key("exists") .send() .await .expect("object exists"); let data = result1.body.collect().await.expect("successful read").to_vec(); assert_eq!(data, b"found"); // Test the "not-exists" case let result2 = s3 .get_object() .bucket("test-bucket") .key("not-exists") .send() .await; assert!(result2.is_err()); assert!(matches!(result2.unwrap_err().into_service_error(), GetObjectError::NoSuchKey(_))); }
Prácticas recomendadas
Cuando se usa aws-smithy-mocks para realizar pruebas.
-
Coincidencia de solicitudes específicas: utilice
match_requests()para asegurarse de que las reglas solo se aplican a las solicitudes previstas, en particular conRuleMode:::MatchAny. -
Verificación del uso de las reglas: compruebe
rule.num_calls()para asegurarse de que las reglas se hayan utilizado realmente. -
Prueba de la gestión de errores: cree reglas que devuelvan errores para comprobar cómo gestiona el código los errores.
-
Prueba de la lógica de reintentos: utilice secuencias de respuesta para comprobar que el código gestiona correctamente cualquier clasificador de reintentos personalizado u otro comportamiento de los reintentos.
-
Centrado en las pruebas: cree pruebas independientes para diferentes escenarios en lugar de tratar de abarcar todo en una sola prueba.