Send actions para UIControl em Testes

Como verificar se o target do UIControl está corretamente configurado?

Matheus de Vasconcelos
Accenture Digital Product Dev
5 min readJun 3, 2020

--

No desenvolvimento iOS é comum adicionarmos às nossas telas diversas especializações da classe UIControl, como botões e sliders, para lidarmos com ações. E, para isso, costumamos adicionar targets a elas. Neste post, vamos entender o que significa tudo isso e como verificar se o target do UI Control está configurado direitinho. Bora lá?

Adicionando uma ação a um UIControl

Para lidar com ações nas classes de UIControl no desevolvimento iOS, costumamos adicionar targets a elas, assim:

Exemplo de como adicionar um target a um UIButton (especialização de UIControl)

Mas o que realmente está acontecendo?

O comando de adicionar um target é realmente muito comum, mas afinal, quais são os parâmetros que passamos com ele? Para entendê-los completamente é preciso compreender um conceito muito importante do Runtime, o Selector.

Selector

A selector is the name used to select a method to execute for an object, or the unique identifier that replaces the name when the source code is compiled. A selector by itself doesn’t do anything. It simply identifies a method.

Como podemos ver pela definição dada na documentação, o selector é na verdade o nome de um método de algum objeto. Ele é criado em tempo de compilação, pegando os nomes dos métodos e transformando na struct chamada Selector.

É importante entender isso porque, durante o desenvolvimento, se conseguirmos acessar o selector de um método podemos por consequência executá-lo sem de fato precisarmos ter/saber a sua implementação.
Essa abordagem é muito comum no Runtime, quando não temos controle exato dos objetos. Assim, a qualquer momento da execução, desde que tenhamos um Selector, podemos executá-lo. Porém, é importante ressaltar que a aplicação desse conceito só é possível se o método for exposto ao compilador do Objective-C, o grande responsável por lidar com as execuções de Runtime.

Voltando aos parâmetros

Em ordem, temos os seguintes parâmetros:

  • target: parâmetro opcional que serve para especificar qual objeto deve lidar com a action que será executada para um dado controlEvent.
  • action: um Selector, portanto, o nome de um método. Um detalhe importante é que por se tratar de um Selector, o método passado aqui deve ser exposto para o Obj-c. Por isso, ao passar essa referência devemos adicionar a marcação @objc ao método.
  • controlEvent: evento que o control vai receber para que ele execute a action do target especificado.

Vendo as definições, podemos perceber como todos eles se conectam para realizar o fluxo da ação no UIControl.

Fluxo de uma ação para um dado UIControl

Como validar que o Control de fato está executando a action que deveria?

Se você acompanha o blog, deve saber que uma parte extremamente importante do desenvolvimento é a fase de testes. Como validar que o comportamento é o esperado?

Para responder à pergunta é necessário validar se o seu target de testes possui um Host Application. Para isso, basta ir na aba "General" do target de testes do Xcode.

Com Host Application

Se o seu target de testes tem um Host Application, este é o cenário ideal para validá-lo. Para isso, basta utilizar a seguinte função do UIControl:

Função disponibilizada pelo UIKit para chamar as ações de um UIControl

Essa função vai enviar o evento especificado para oUIControl, que por sua vez vai chamá-la e executar as actions adicionadas para o target. Assim, um teste que valida uma ação pode ser feito dessa forma:

Sem Host Application

Caso o target de testes não possua um Host, a abordagem anterior não vai funcionar. A explicação para isso se encontra na própria documentação do UIControl:

Action methods are dispatched through the current UIApplication object, which finds an appropriate object to handle the message, following the responder chain if needed.

Ou seja, as ações enviadas no UIControl serão processadas pelo objeto UIApplication, porém, como não há um host application, esse objeto não existe. Dessa forma, ao tentar enviar uma action, nada vai acontecer e seus testes vão falhar, mesmo que a action do control esteja corretamente configurada.

Analisando o sendActions, percebemos que ele basicamente executa um selector de um target com a seguinte particularidade: o envio e processamento dessa execução acontece em um objeto que nem sempre está disponível. Neste contexto, porém, a aplicação de alguns conceitos de Runtime torna possível replicar esse comportamento, sem processá-lo nesse objeto. Para fazer isso, utilize a seguinte implementação:

Explicação

O primeiro passo é pegar os targets que foram adicionados para o control - lembrando que os targets são os responsáveis por lidar com as actions que o control executa.

A partir deles é possível pegar cada uma das actions, por meio da função actions, que respondem para o evento que estamos disparando.

Além disso, é possível recuperar o objeto que é responsável por executar essas actions com a propriedade base do target.

Com isso, temos as ações e o objeto que as executa. Assim, basta lembrar que, por meio do Runtime — desde que tenhamos um Selector - podemos executá-lo. Para isso, transformamos as actions em selectors e validamos se o objeto pode executá-lo. Se ele puder, executamos.

Para fazer a transformação utilizamos um map e para validar se o objeto pode executar o Selector utilizamos a função responds(to: ) . Se ela retornar como verdadeiro, o executamos com a função perform(selector).

Bônus

A implementação acima deve resolver a maioria dos casos, porém, se a função que o Selector representa possuir algum parâmetro, esse código vai quebrar. Para resolver isso devemos passar esse parâmetro para o Selector.

De modo geral, como esse parâmetro costuma ser o próprio UIControl que possui o target, basta passá-lo pela própria função perform(selector, with: parameter).
Assim, uma implementação para o caso acima seria a seguinte:

Portanto, independente se há um host ou não no target de testes, podemos validar o comportamento dos UIControl do projeto, tendo um teste da seguinte forma:

E é isso! Ficou com alguma dúvida ou tem algo a dizer? Deixe um comentário! Se você quiser estudar mais sobre o assunto, pode acessar os links abaixo, que eu usei para escrever este texto:

E se você quiser fazer parte do nosso time, é só dar uma olhada aqui, se candidatar a uma vaga e vamos aprender juntos! Até a próxima.

--

--

Matheus de Vasconcelos
Accenture Digital Product Dev

iOS Developer — Apple Developer Academy Alumni | Mackenzie. Studying Unit Tests.