Escrevendo Testes Unitários para uma ViewController com TableView
É 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
.
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.
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.
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
.
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.
Também podemos escrever testes que nos assegurem que as classes responsáveis pelo delegate
e dataSource
da tableView sejam a HomeViewController
.
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.
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.
Agora basta que a HomeViewModel
assine esse protocolo, para que seja possível passá-la no inicializador da controller.
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.
Feito isso, agora iremos testar a situação em que a API retorna dados.
Em nossa classe de testes, iremos criar uma propriedade globallet 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.
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.
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
.
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.
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
.
Agora basta usar este espião no nosso teste para vermos se de fato estamos dando push em uma DetailViewController
.
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:
Com isso, finalizamos o último teste desse post.
Se essa publicação foi útil, clique no Clap! Obrigado!