Gerar simulações automaticamente usando o mockall no AWS SDK para Rust - AWS SDK para Rust

Gerar simulações automaticamente usando o mockall 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. Você pode gerar automaticamente a maioria das implementações de simulação de que seus testes precisam usando a popular automock da caixa mockall.

Este exemplo testa um método personalizado chamado determine_prefix_file_size(). Esse método chama um método de wrapper list_objects() personalizado que chama o Amazon S3. Ao simular list_objects(), o método determine_prefix_file_size() pode ser testado sem realmente entrar em contato com o Amazon S3.

  1. Em um prompt de comando para o diretório do seu projeto, adicione a caixa mockall como uma dependência:

    $ cargo add --dev mockall

    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.

    $ cargo add aws-sdk-s3

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

  2. Inclua o módulo automock da caixa mockall.

    Inclua também quaisquer outras bibliotecas relacionadas ao AWS service (Serviço da AWS) que você está testando, neste caso, o Amazon S3.

    use aws_sdk_s3 as s3; #[allow(unused_imports)] use mockall::automock; use s3::operation::list_objects_v2::{ListObjectsV2Error, ListObjectsV2Output};
  3. Em seguida, adicione o código que determina qual das duas implementações da estrutura de wrapper da aplicação do Amazon S3 usar.

    • A verdadeiro foi gravada para acessar o Amazon S3 pela rede.

    • A implementação simulada gerada é por mockall.

    Neste exemplo, a que é selecionada recebe o nome de S3. A seleção é condicional com base no atributo test:

    #[cfg(test)] pub use MockS3Impl as S3; #[cfg(not(test))] pub use S3Impl as S3;
  4. A estrutura S3Impl é a implementação da estrutura de wrapper do Amazon S3 que realmente envia solicitações para a AWS.

    • Quando os testes estão habilitados, esse código não é usado porque a solicitação é enviada para a simulação e não para a AWS. O atributo dead_code diz ao linter que não relate um problema se o tipo S3Impl não for usado.

    • A condicional #[cfg_attr(test, automock)] indica que, quando os testes estão habilitados, o atributo automock deve ser definido. Isso faz com que mockall gere uma simulação de S3Impl que será nomeada MockS3Impl.

    • Neste exemplo, o método list_objects() é a chamada que você deseja simular. automock criará automaticamente um método expect_list_objects() para você.

    #[allow(dead_code)] pub struct S3Impl { inner: s3::Client, } #[cfg_attr(test, automock)] impl S3Impl { #[allow(dead_code)] pub fn new(inner: s3::Client) -> Self { Self { inner } } #[allow(dead_code)] pub async fn list_objects( &self, bucket: &str, prefix: &str, continuation_token: Option<String>, ) -> Result<ListObjectsV2Output, s3::error::SdkError<ListObjectsV2Error>> { self.inner .list_objects_v2() .bucket(bucket) .prefix(prefix) .set_continuation_token(continuation_token) .send() .await } }
  5. Crie as funções de teste em um módulo chamado test.

    • A condicional #[cfg(test)] indica que mockall deve construir o módulo de teste se o atributo test for true.

    #[cfg(test)] mod test { use super::*; use mockall::predicate::eq; #[tokio::test] async fn test_single_page() { let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); // Verify we got the correct total size back assert_eq!(7, size); } #[tokio::test] async fn test_multiple_pages() { // Create the Mock instance with two pages of objects now let mut mock = MockS3Impl::default(); mock.expect_list_objects() .with(eq("test-bucket"), eq("test-prefix"), eq(None)) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(5).build(), s3::types::Object::builder().size(2).build(), ])) .set_next_continuation_token(Some("next".to_string())) .build()) }); mock.expect_list_objects() .with( eq("test-bucket"), eq("test-prefix"), eq(Some("next".to_string())), ) .return_once(|_, _, _| { Ok(ListObjectsV2Output::builder() .set_contents(Some(vec![ // Mock content for ListObjectsV2 response s3::types::Object::builder().size(3).build(), s3::types::Object::builder().size(9).build(), ])) .build()) }); // Run the code we want to test with it let size = determine_prefix_file_size(mock, "test-bucket", "test-prefix") .await .unwrap(); assert_eq!(19, size); } }
    • Cada teste usa let mut mock = MockS3Impl::default(); para criar uma instância mock de MockS3Impl.

    • Ele usa o método expect_list_objects() da simulação (criada automaticamente por automock) para definir o resultado esperado para quando o método list_objects() for usado em outro lugar no código.

    • Após estabelecer as expectativas, ele as usa para testar a função chamando determine_prefix_file_size(). O valor retornado é verificado para confirmar se está correto, usando uma afirmação.

  6. A função determine_prefix_file_size() usa o wrapper Amazon S3 para obter o tamanho do arquivo de prefixo:

    #[allow(dead_code)] pub async fn determine_prefix_file_size( // Now we take a reference to our trait object instead of the S3 client // s3_list: ListObjectsService, s3_list: S3, bucket: &str, prefix: &str, ) -> Result<usize, s3::Error> { let mut next_token: Option<String> = None; let mut total_size_bytes = 0; loop { let result = s3_list .list_objects(bucket, prefix, next_token.take()) .await?; // Add up the file sizes we got back for object in result.contents() { total_size_bytes += object.size().unwrap_or(0) as usize; } // Handle pagination, and break the loop if there are no more pages next_token = result.next_continuation_token.clone(); if next_token.is_none() { break; } } Ok(total_size_bytes) }

O tipo S3 é usado para chamar o SDK encapsulado para funções do Rust para oferecer suporte a ambos S3Impl e MockS3Impl ao fazer solicitações HTTP. A simulação gerada automaticamente pelo mockall relata qualquer falha de teste quando os testes são habilitados.

Você pode visualizar o código completo desses exemplos no GitHub.