Tô ligado que nessa semana, provavelmente, você teve algum Pull Request [PR] barrado pelo sonar por conta de Quality Gate [QG].
E é por isso que nessa série de artigos vamos falar de técnicas para teste! Dai você não passa esse perrengue em pré feriado ou na sexta-feira.
Contexto
A série de artigos vai ser dividida em dois períodos: Sem o uso do MockTail [S.M] e Com o uso do MockTail [C.M]. Antes de prosseguirmos para nosso primeiro papo, você pode pensar
Leitor: Pô, Caíque. Mas eu uso {Qualquer outra lib de Mock} e ai?
Pode ficar tranquilo, todos os conceitos apresentados nos artigos [C.M] vão te ajudar a entender seu framework e dai é contigo. Capture them all
Imagina que o seu [QG] falhou por falta de cobertura no método connect do seu Repository.
Dando uma olhada nele, você percebe que lá existe a conexão e a escuta dos eventos de um WebSocket. O código parece ser algo do tipo:
final StreamController<dynamic> _streamController = StreamController<dynamic>();
Stream<dynamic> get events => _streamController.stream;
Future<void> connect(String url) async {
_webSocket = await WebSocket.connect(url, {}, []);
_listen();
}
void _listen(){
_webSocket.listen(
(data) {
_streamController.add(data);
},
onError: (error) {
_streamController.addError(error);
},
onDone: () {
_streamController.close();
}
)
}
É, tu pensou que ia acabar o expediente mais cedo , né? Depois desse artigo, você vai mesmo.
O nosso teste
Para começar nosso teste, vamos precisar adicionar em nosso pubspec.yml
dev_dependencies:
mocktail: ^0.2.0
E teremos algo do tipo
test(
'Mocking emission from webserver`,
() {
when(
() => mockWebSocket.connect(any())
).thenAswer(_) async => mockWebsocket);
when(
() => mockWebSocket.listen(
any(),
onError: any(named: 'onError'),
onDone: any(named: 'onDone'),
)
).thenAnswer(
(invocation) {
final onData = invocation.positionalArguments[0] as void Function(dynamic);
final onError = invocation.namedArguments[#onError] as void Function(Object);
final onDone = invocation.namedArguments[#onDone] as void Function();
return streamController.stream.listen(onData, onError: onError, onDone: onDone);
},
);
await repository.connect(url);
expectLater(repository.events, emits('Hello'));
streamController.add('Hello');
},
);
Pronto, você já pode sextar! Mas, antes de ir, da um confere em como funciona a função when do mocktail.
A estrutura da função When
A estrutura da função when sempre será algo do tipo
when(() => objectoMockado.nomeDoMetodo(arg1, arg2, ...)).thenAnswer(
(invocation) {
// Voce pode fazer qualquer coisa aqui desde que você retorne o tipo indicado pelo método `nomeDoMetodo`
},
);
Algumas vezes, você verá que ao invés de passar o argumento, a pessoa passará uma função chamada any(). Ela tem como funcionalidade realizar o match de qualquer argumento na etapa de estruturação do seu mock.
Apesar de ela não ligar para o argumento, você pode ser um pouco mais específico em seu uso, isso ocorre através dos atributos named e that.
Especificando através do named
Permite que você realize o match através de um argumento nomeado.
Leitor: Que beleza! Vou usar isso em todos meus setup de mocks.
Calma lá. Existem alguns cenários no qual o uso do named irá levar a erros. Por exemplo,
void exampleFunction(int a, {int? b}) {
print("a: $a, b: $b");
}
Agora imagine que queremos realizar o mock dessa função. Na abordagem do nosso leitor, teríamos:
when(mock.exampleFunction(any(named: 'a'))).thenReturn('someValue');
Esse trecho de código retornará um erro, pois o argumento que estamos tentando realizar o ‘match’ não é nomeado.
Ajustando esse setup, teríamos:
when(mock.exampleFunction(1, b: any(named: 'b'))).thenReturn('someValue');
Mas o que aconteceria se precisássemos realizar o mock de um atributo dado uma determinada condição?
Especificando através de that
Quando precisamos aplicar condições ao nosso setup, podemos utilizar o atributo that ao invés do named ou com ele.
Esse atributo permite que passemos ‘Matchers’ para realizar o ‘Match’. Pô, frase complexa. Bora lá dar uma olhada nesse caso para termos um melhor entendimento.
Imagine que estamos mockando um callback. Caso o parâmetro do callback seja par, então iremos retornar um valor. Já, caso ele seja ímpar, retornaremos outro. Para se obter esse comportamento:
when(mock.someFunction(any(that: (int x) => x % 2 == 0))).thenReturn('Even');
when(mock.someFunction(any(that: (int x) => x % 2 != 0))).thenReturn('Odd');
Agora sabemos como fazer os Matches necessários para concluirmos o setup do nosso Mock. Porem como controlamos o que ele retornará ?
Controlando o retorno do nosso objeto de mock
Quando precisamos de um controle maior sobre o que precisamos retornar, podemos utilizar o thenAnswer.
Olhando para a sua assinatura, vemos que o seu retorno é um callback, o qual possui uma variável do tipo Invocation.
Esse objeto contém todos os metadados relacionados à invocação daquela chamada para o mock.
Tá, estava fácil, mas parece que complicou. Vamos tentar entender esse método através de duas explicações: Em uma delas iremos utilizar o thenAnswer para lidar com futures e a segunda na qual iremos interceptar os valores passados para o mock e compor o resultado dele.
Utilizando thenAnswer para retorno de futures:
Caso a função do seu mock retorne um Future, o thenAnswer pode te ajudar nesse caso. Por exemplo:
when(() => meuMock.metodoAssincrono()).thenAnswer((\_) async => valorRetornado);
Utilizando thenAnswer para interceptar valores passados para o mock
Outra utilidade poderosa do thenAnswer é a capacidade de acessar os argumentos que foram passados para o método mockado.
Isso é feito através do objeto Invocation que é passado para o callback. Por exemplo:
when(() => meuMock.metodoComArgumentos(any())).thenAnswer((invocation) {
final arg = invocation.positionalArguments\[0];
return 'Valor baseado em $arg';
});
Neste exemplo, interceptamos o argumento passado para metodoComArgumentos e retornamos um valor baseado nele.
É isso ai
Bom, até agora vimos o uso do ´when´ para a estruturação dos comportamentos do nosso objeto de mock. Ainda nesse tema, nos aprofundamos em utilizar uma técnica de ´matching´ para termos maior flexibilidade na hora do setup.
Os próximos tópicos vão englobar um pouco mais sobre teoria de teste e criação de um repositório com os cenários mais comuns que você enfrentará no seu dia a dia.
Mocktail - Pub Dev