Escrevendo Testes Unitários para uma ViewController com TableView

Leonardo Cunha
Usemobile
5 min readJul 20, 2022

--

É bem conhecida pela comunidade a importância dos testes unitários: além de identificar possíveis bugs antes do projeto subir para produção, eles documentam as regras de negócios das cenas, e tem um papel importante na refatoração do código — o que é natural com o passar do tempo.

Neste post, iremos escrever testes unitários para uma ViewController que exibe dados vindos de uma API em uma TableView.

Ver respositório do projeto no github

HomeViewController

Primeiramente, vamos conhecer a HomeViewController que contém a TableView. Afinal, é para essa classe que vamos escrever os testes.

De início, podemos observar que ela possui um array de Person inicialmente nulo, e cria uma instancia deHomeViewModel.

Imagem 1 — HomeViewController

No viewDidLoad(), estamos dando um setup na tableView, e chamando o getPersons() que faz o consumo da API através da viewModel.
O retorno é o array de Person cujo os dados queremos exibir na tableView.

Imagem 2 — HomeViewModel

A HomeViewController também está responsável por implementar os métodos de UITableViewDataSource.

Note que no método cellForRowAt estamos dando um setup nas células, e nodidSelectRowAt o usuário navega para uma tela de Detalhes, que recebe um objeto Person no inicializador.

Imagem 3 — HomeViewController

Obs. : É possível delegar a responsabilidade de implementação dos métodos de DataSource a uma classe externa aHomeViewController.

Preparando os testes

No target de testes, podemos criar um novo arquivo e nomeá-lo HomeViewControllerTests. Pra isso, você deve criar um target de testes.

Dentro dele, podemos criar uma propriedade sut (system under test) que recebe uma instância da HomeViewController.

Imagem 4 — HomeViewControllerTests

Ao acessar na memória a propriedade view da HomeViewController, o métodoloadView() será automaticamente chamado, e por consequência, o viewDidLoad(). De acordo com a documentação da Apple não é recomendado chamar o loadView() diretamente.

Com isso, nosso sut é uma instância de HomeViewController que já foi carregada.

Escrevendo os testes

Aqui espera-se um código limpo, facilitando o entendimento de quem irá ler os testes. Afinal, queremos que numa futura refatoração esse código seja um apoio, e não um empecilho. Geralmente, os nomes das funções de testes tendem a ser maiores de forma que fique mais intuitivo o cenário do teste e o resultado esperado.

Vamos aos testes:

Podemos criar um teste que verifica se a TableView existe ao carregar a tela.

Imagem 5— Função de teste

Também podemos escrever testes que nos assegurem que as classes responsáveis pelo delegate e dataSource da tableView sejam a HomeViewController.

Imagem 6 — Função de teste

Que tal testarmos se a tableView está exibindo corretamente todos os dados que chegam da nossa API?

Parece bom, entretanto não seria uma boa ideia depender da API real para nossos testes rodarem. Também não queremos sobrecarregar o servidor ou deletar usuários reais quando rodarmos os nossos testes.

Criando um Spy para a HomeViewModel

Temos que simular uma API que nos retorne os dados esperados. Uma solução é usar um spy para a HomeViewModel. O spy é uma classe “fake” que pode simular o comportamento de produção, porém, onde criamos variáveis para espionar o comportamento dos métodos. No nosso caso, a ideia é que aHomeViewModelSpy retorne no getPersons() os dados que passarmos para ela na propriedadedataToBeReturned de forma síncrona.

Imagem 7 — HomeViewModel Spy

Perceba que estamos conformando nosso Spy com um protocolo HomeViewModelProtocol. Isso se faz necessário, pois como queremos usá-la na HomeViewController nos cenários de testes, será necessário injetá-la no inicializador da classe.

A partir de agora, a HomeViewController passa a depender desse protocolo (ou interface). E passamos a instanciar a HomeViewModel apenas na inicialização default.

Imagem 8 — Injeção de dependência

Agora basta que a HomeViewModel assine esse protocolo, para que seja possível passá-la no inicializador da controller.

Imagem 9 — Assinatura do protocolo

Voltando aos testes

Devemos verificar se num momento anterior ao retorno da API, quando ainda não há dados para exibir, a tableView de fato não exibe nenhuma linha. Afinal, este é o comportamento esperado.

Imagem 10 — Função de teste

Feito isso, agora iremos testar a situação em que a API retorna dados.

Em nossa classe de testes, iremos criar uma propriedade global
let viewModelSpy = HomeViewModelSpy().

Vamos ao teste (imagem 11): dada uma HomeViewController que recebe a viewModelSpy, e dado que o nosso Spy retorna um array persons com dois objetos Person que estamos criando. Quando a view carregar, a tableView deve ter uma única sessão, e ter número de linhas igual ao número de objetos Person dentro do array. No caso, duas.

Imagem 11 — Função de teste

Legal, mais um teste foi escrito!

Fixtures

Antes de continuar, vamos facilitar a criação dos objetos Person através das fixtures. Fixtures são métodos que extendem de um tipo de dado, e que simplificam a inicialização default de um objeto desse tipo, ao atribuir valores default.
Isso deixará o código mais limpo, pois não precisaremos passar nenhum parâmetro para criar objetos dummys, e quando quisermos testar uma propriedade específica, podemos passar apenas ela, ficando claro o entendimento para o leitor.

Imagem 12 — Fixture de Person

Escrevendo mais testes

Que tal escrevermos um teste para assegurar o tipo de célula que estamos criando na TableView. Se tratando da Home, o comportamento esperado é que sejam criadas HomeTableViewCell.

Imagem 13— Função de teste

Aqui também vamos escrever um teste para um comportamento interior a celula: ela preenche uma label com os dados de Person. Vamos verificar se esse texto está sendo escrito conforme o esperado.

Imagem 14 — Função de teste

Se fosse uma célula mais complexa, nós poderíamos criar uma nova classe de testes apenas para a célula.

O último método da tableView que precisamos testar é o didSelectRowAt, onde é dado um pushViewController passando uma DetailsViewController.

Criando um Spy para a NavigationController

Para que possamos verificar se a função que escrevemos está dando pushViewController corretamente, uma alternativa é criar uma nova NavigationController oberservável (ou seja, um Spy), e passar a nossa HomeViewController como rootViewController, e então, verificar o que está sendo passado como parâmetro no método pushViewController.

Imagem 15 — NavigationController Spy

Agora basta usar este espião no nosso teste para vermos se de fato estamos dando push em uma DetailViewController.

Imagem 16 — Função de teste

Verificamos o tipo da ViewController. Mas também é necessário testarmos se estamos passamos o objeto Person correto no inicializador. Neste caso a propriedade person de DetailsViewController está acessível de fora da classe, então podemos escrever:

Imagem 17 — Função de teste

Com isso, finalizamos o último teste desse post.
Se essa publicação foi útil, clique no Clap! Obrigado!

Referência

SANTOS, Leonardo — Become a Unit Test Master

--

--