Na saga da criação do aplicativo petnotes, acabei caindo em um problema com os testes. O aplicativo possui um conjunto de testes unitários, e alguns destes testes necessitam mockar dependências externas(requisições a bancos de dados ou API, por exemplo).

O problema veio quando realizei atualizações do Flutter e este começou a utilizar a nova funcionalidade do dart que trata valores null, basicamente foi necessária uma reescrita de parte do código para trabalhar com essa funcionalidade, o problema mesmo foi que todos os testes que faziam uso do pacote mockito passaram a não funcionar. Existiam alguns possíveis caminhos mas todos se mostraram muito trabalhosos ou simplesmente não funcionaram, foi aí que depois de alguma busca cheguei no pacote chamado mocktail.

O pacote tinha acabado de chegar na sua versão 1.0, e parecia um bom momento para iniciar o uso do mesmo. Vamos considerar um teste de exemplo que usa um banco de dados, escrevendo este teste com o mockito teríamos algo assim:

@GenerateMocks([Database])
void main() {
  group('PetSQLite =>', () {
    PetRepository petRepository;
    Database mockDatabase;

    setUp(() {
      mockDatabase = new MockDatabase();
      petRepository = new PetSQLite(mockDatabase);
    });

    group('create', () {
      test('should add a pet to the database', () async {
        final pet = Pet('name', 'specie');
        when(mockDatabase.insert(any, any,
                conflictAlgorithm: anyNamed('conflictAlgorithm')))
            .thenAnswer((_) async {
          return 1;
        });
        final response = await petRepository.create(pet);

        expect(response, pet);
      });

    });

  });
}

Neste teste estamos fazendo uso do any do pacote mockito para dizer que qualquer dado que for passado naquela posição a resposta do mock deve ser a mesma, é justamente o uso desse any que fez o pacote não funcionar nas novas versões do dart. Basicamente o teste a ser feito era converter o teste feito em mockito para o pacote mocktail. A conversão direta seria algo do tipo:

class MockDatabase extends Mock implements Database {}

void main() {
  group('PetSQLite =>', () {
    late PetRepository petRepository;
    late Database mockDatabase;

    setUp(() {
      mockDatabase = new MockDatabase();
      petRepository = new PetSQLite(mockDatabase);
    });

    group('create', () {
      test('should add a pet to the database', () async {
        final pet = Pet('name', 'specie');

        when(
          () => mockDatabase.insert(
            any(),
            any(),
            conflictAlgorithm: any(named: 'conflictAlgorithm'),
          ),
        ).thenAnswer((_) async {
          return 1;
        });

        final response = await petRepository.create(pet);

        expect(response, pet);
      });

    });

    ;
  });
}

Vemos aqui pequenas modificações no código:

  • A geração do mock é feita herdando da classe Mock e não usando uma anotação que vai gerar um arquivo separado depois.
  • Basicamente a mudança do ponto de vista do uso do pacote foi a forma do uso do any, aqui ele é uma função, e a mesma é utilizada também nos parâmetros nomeados.
  • Ao passaro mock para a função when é necessário passar dentro de uma função.

Com essas mudanças o código passas a funcionar perfeitamente, particularmente achei a mudança de pacote mais fácil do que tentar implementar as correções necessárias para o mockito voltar a funcionar. Outro ponto interessante é que com o novo pacote, a quantidade de pacotes instalados simplesmente para rodar testes foi reduzida, com o mockito eram necessários dois pacotes.

O novo pacote se mostrou uma boa alternativa e provavelmente ficarei fazendo uso do mesmo por um bom tempo nos próximos projetos envolvendo dart.