Teste de unidade com aws-smithy-mocks no AWS SDK para Rust - AWS SDK para Rust

Teste de unidade com aws-smithy-mocks no AWS SDK para Rust

O AWS SDK para Rust fornece várias abordagens para testar um código que interage com os Serviços da AWS. Este tópico descreve como usar a caixa aws-smithy-mocks, que oferece uma maneira simples, mas poderosa, de simular as respostas do cliente do AWS SDK para fins de teste.

Visão geral

Ao escrever testes para códigos que usam os Serviços da AWS, você geralmente quer evitar fazer chamadas de rede reais. A caixa aws-smithy-mocks fornece uma solução, permitindo que você:

  • Crie regras simuladas que definem como o SDK deve responder a solicitações específicas.

  • Retorne diferentes tipos de respostas (sucesso, erro, respostas HTTP).

  • Combine solicitações com base em suas propriedades.

  • Defina sequências de respostas para testar o comportamento de nova tentativa.

  • Verifique se suas regras foram usadas conforme o esperado.

Adicionar a dependência

Em um prompt de comando para o diretório do projeto, adicione a caixa aws-smithy-mocks como uma dependência:

$ cargo add --dev aws-smithy-mocks

Usar a opção --dev adiciona a caixa à seção [dev-dependencies] do seu arquivo Cargo.toml. Como uma dependência de desenvolvimento, ela não é compilada e incluída no binário final usado para o código de produção.

Esse código de exemplo também usa o Amazon Simple Storage Service como o AWS service (Serviço da AWS) de exemplo e requer um recurso test-util.

$ cargo add aws-sdk-s3 --features test-util

Isso adiciona a caixa à seção [dependencies] do seu arquivo Cargo.toml.

Uso básico

Consulte um exemplo simples de como usar o aws-smithy-mocks para testar o código que interage com o 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); }

Criar regras de simulação

As regras são criadas usando a macro mock!, que usa uma operação do cliente como o argumento. Em seguida, você pode configurar como a regra deve se comportar.

Solicitações correspondentes

Você pode especificar mais as regras combinando as propriedades da solicitação:

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 resposta diferentes

É possível retornar tipos de regras:

// 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") ) });

Testar o comportamento de nova tentativa

Um dos recursos mais poderosos do aws-smithy-mocks é a capacidade de testar o comportamento de nova tentativa definindo sequências de respostas:

// 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 regra

Você pode controlar como as regras são combinadas e aplicadas usando os modos de regra:

// 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]);

Exemplo: testar o comportamento de nova tentativa

Aqui está um exemplo mais completo que mostra como testar o comportamento de nova tentativa:

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); }

Exemplo: respostas diferentes com base nos parâmetros da solicitação

Você também pode criar regras que retornem respostas diferentes com base nos parâmetros da solicitação:

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, &not_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áticas recomendadas

Ao usar o aws-smithy-mocks para testes:

  1. Combine solicitações específicas: use match_requests() para garantir que suas regras se apliquem somente às solicitações pretendidas, em particular com RuleMode:::MatchAny.

  2. Verifique o uso das regras: verifique rule.num_calls() para garantir que as regras foram realmente usadas.

  3. Teste o tratamento de erros: crie regras que retornem erros para testar como seu código lida com falhas.

  4. Teste a lógica de nova tentativa: use sequências de resposta para verificar se seu código manipula corretamente qualquer classificador ou outro comportamento de nova tentativa.

  5. Mantenha testes focados: crie testes separados para cenários diferentes em vez de tentar abordar tudo em um único teste.