<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: vitorbranco</title>
    <description>The latest articles on Forem by vitorbranco (@vitorbranco).</description>
    <link>https://forem.com/vitorbranco</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1112704%2F580b5554-d9b0-4da2-baa1-e5a20c9cdf99.png</url>
      <title>Forem: vitorbranco</title>
      <link>https://forem.com/vitorbranco</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/vitorbranco"/>
    <language>en</language>
    <item>
      <title>Como testar um LiveData?</title>
      <dc:creator>vitorbranco</dc:creator>
      <pubDate>Thu, 13 Jul 2023 17:09:36 +0000</pubDate>
      <link>https://forem.com/comunidadedevspace/como-testar-um-livedata-3g06</link>
      <guid>https://forem.com/comunidadedevspace/como-testar-um-livedata-3g06</guid>
      <description>&lt;p&gt;A criação de aplicativos modernos e eficientes é fundamental para proporcionar uma experiência excepcional ao usuário. Nesse sentido, a arquitetura MVVM (Model-View-ViewModel) tem se destacado como uma abordagem poderosa para o desenvolvimento de aplicativos Android, permitindo uma separação clara das responsabilidades e facilitando a manutenção e escalabilidade do código. Dentro dessa arquitetura, um dos componentes fundamentais é o LiveData.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;O que é LiveData?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;O LiveData é um classe observável que permite a comunicação eficiente entre as camadas do aplicativo, tornando possível a atualização automática da UI com base em mudanças nos dados em tempo real.&lt;br&gt;
Algumas vantagens do uso do LiveData, além das atualizações automáticas da UI, são:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gerenciamento de ciclo de vida: o LiveData integra-se com o ciclo de vida dos componentes do Android, que evita problemas comuns, como vazamentos de memória.&lt;/li&gt;
&lt;li&gt;Dados sempre atualizados: quando um ciclo de vida se torna inativo, ele recebe os dados mais recentes quando se tornar ativo novamente, como no caso de uma rotação de tela.&lt;/li&gt;
&lt;li&gt;Tratamento de eventos assíncronos: o LiveData oferece um mecanismo para observar e reagir a eventos assíncronos, como uma chamada de rede, por exemplo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Agora que compreendemos sua importância, como garantir que nosso LiveData está transmitindo as informações corretamente? Vamos ver a seguir como realizar testes unitários efetivos para garantir a qualidade e a estabilidade do código contendo LiveData.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Como testar um LiveData?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Para testar um LiveData é muito importante lembrar que o LiveData é uma classe observável, ou seja, depende de um &lt;code&gt;Observer&lt;/code&gt; para atualizar os dados. A maioria dos erros ao escrever um teste é esquecer de observá-lo no teste. Assim, são necessários alguns passos para escrever o teste de maneira assertiva, que veremos a seguir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Criando um ViewModel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Neste artigo, iremos usar como exemplo um ViewModel (AlunoViewModel) que define um MutableLiveData de String, e que tem uma função chamada &lt;code&gt;setNomeDoAluno()&lt;/code&gt; que é responsável por alterar o nome de um aluno hipotético, que está guardado no LiveData:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AlunoViewModel: ViewModel() {
    private val _alunoLiveData = MutableLiveData&amp;lt;String&amp;gt;()
    val alunoLiveData: LiveData&amp;lt;String&amp;gt; = _alunoLiveData

    fun setNomeDoAluno(nome: String) {
        _alunoLiveData.value = nome
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Criando o teste&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Dentro do diretório de testes, criamos uma classe AlunoViewModelTest e dentro dela definimos uma variável &lt;code&gt;underTest&lt;/code&gt; que é uma instância de AlunoViewModel para podermos utilizar durante os testes.&lt;/p&gt;

&lt;p&gt;Escrevemos então a função &lt;code&gt;isLiveDataWorking()&lt;/code&gt; com a notação &lt;code&gt;@Test&lt;/code&gt; (da biblioteca do JUnit), e nela utilizamos a função &lt;code&gt;setNomeDoAluno()&lt;/code&gt; para alterar o nome do aluno e passá-lo para o LiveData. Em seguida usamos o assertEquals para comparar o valor emitido pelo LiveData com o valor esperado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AlunoViewModelTest {
    private val underTest: AlunoViewModel by lazy {
        AlunoViewModel()
    }

    @Test
    fun `isLiveDataWorking`() {
        underTest.setNomeDoAluno("Vitor")
        assertEquals(underTest.alunoLiveData.value, "Vitor")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao rodar o teste acima, de cara temos o nosso primeiro erro! Não se assuste, esse erro é extremamente comum.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qKX1uRS0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnh96surykjf5yn8sqis.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qKX1uRS0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnh96surykjf5yn8sqis.png" alt="Imagem com o erro 'Method getMainLooper in android.os.Looper not mocked.'" width="800" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Esse erro ocorre porque, por padrão, o LiveData executa as tarefas em segundo plano usando um executor em uma thread separada, o que pode causar problemas nos testes unitários, pois os resultados podem não estar prontamente disponíveis para verificação.&lt;/p&gt;

&lt;p&gt;Para resolver esse problema, nós usamos a regra InstantTaskExecutorRule, que veremos a seguir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;InstantTaskExecutorRule&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Essa é uma regra de testes usada para substituir o comportamento padrão do LiveData relacionado à execução de tarefas em segundo plano. O InstantTaskExecutorRule garante que as tarefas sejam executadas imediatamente no encadeamento de teste, permitindo que os testes obtenham resultados corretos e façam as verificações necessárias.&lt;/p&gt;

&lt;p&gt;Para utilizá-lo, basta adicionar a regra da seguinte maneira:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AlunoViewModelTest {

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    private val underTest: AlunoViewModel by lazy {
        AlunoViewModel()
    }

    @Test
    fun `isLiveDataWorking`() {
        underTest.setNomeDoAluno("Vitor")
        assertEquals(underTest.alunoLiveData.value, "Vitor")
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora sim, nosso teste estará funcionando.&lt;br&gt;
Mas e caso eu precise fazer alguma transformação no meu LiveData? Suponhamos que eu precise de um LiveData que me dê o número de caracteres do nome do aluno:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AlunoViewModel: ViewModel() {
    private val _alunoLiveData = MutableLiveData&amp;lt;String&amp;gt;()

    // Mapeia os dados do alunoLiveData para retornar o número de caracteres
    val numeroDeCaracteresLiveData = _alunoLiveData.map { it.length }

    fun setNomeDoAluno(nome: String) {
        _alunoLiveData.value = nome
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao realizar o mesmo procedimento do teste que fizemos com o alunoLiveData, alterando o nosso assertEquals, temos outro erro:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;assertEquals(underTest.numeroDeCaracteresLiveData.value, 5)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XFwEiz7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3lluj6lfh5zslh43h38.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XFwEiz7r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f3lluj6lfh5zslh43h38.png" alt="Imagem do teste falho: nosso resultado esperado não bateu com o resultado obtido." width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;O teste falhou, uma vez que o LiveData nos retornou null como valor, ao invés de 5 que era o esperado para o aluno "Vitor". Isso acontece porque, apenas ler o valor de numeroDeCaracteresLivedata.value não inicia a corrente de transformações pois ele não está sendo &lt;strong&gt;observado&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;No primeiro teste que fizemos, o alunoLiveData teve seu valor lido pois ele é um MutableLiveData e nenhuma transformação foi feita nele.&lt;/p&gt;

&lt;p&gt;Vamos resolver esse problema colocando um Test Helper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Criando um Test Helper&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Poderíamos utilizar a função &lt;code&gt;observeForever()&lt;/code&gt; para observar os dados do LiveData durante o teste, mas isso não é o mais adequado.&lt;/p&gt;

&lt;p&gt;É mais recomendado recorrer a um Test Helper, que nada mais é do que uma função a ser acrescentada na classe LiveData que permite buscar o valor do nosso LiveData por um tempo determinado e, depois de encontrado o valor, remover o &lt;code&gt;Observer&lt;/code&gt; (coisa que o &lt;code&gt;observeForever()&lt;/code&gt; não faz).&lt;/p&gt;

&lt;p&gt;Além disso, o Test Helper exibe um erro caso nenhum valor tenha sido obtido após determinado tempo, o que impede que o teste fique em execução eternamente.&lt;/p&gt;

&lt;p&gt;Crie um novo arquivo (do tipo File mesmo) e adicione o código fornecido pela Google para ajudar nos testes de LiveData:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fun &amp;lt;T&amp;gt; LiveData&amp;lt;T&amp;gt;.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer&amp;lt;T&amp;gt; {
        override fun onChanged(value: T) {
            data = value
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }

    this.observeForever(observer)

    if (!latch.await(time, timeUnit)) {
        throw TimeoutException("LiveData value was never set.")
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Não se esqueça de adicionar corretamente todas as dependências necessárias nesse arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A função &lt;code&gt;getOrAwaitValue()&lt;/code&gt; não existe na classe LiveData, mas com esse código nós estamos adicionando ela nessa classe, e então podemos usá-la no nosso teste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class AlunoViewModelTest {

    @get:Rule
    val instantTaskExecutorRule = InstantTaskExecutorRule()

    private val underTest: AlunoViewModel by lazy {
        AlunoViewModel()
    }

    @Test
    fun `isLiveDataWorking`() {
        underTest.setNomeDoAluno("Vitor")
        assertEquals(underTest.numeroDeCaracteresLiveData.getOrAwaitValue(), 5)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ao rodar o teste dessa maneira, temos então o nosso tão almejado "Tests passed"!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nn29uSJF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mue6vwi1yc5rnt47lnd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nn29uSJF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mue6vwi1yc5rnt47lnd7.png" alt='Resultado exibindo "Tests passed"' width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sobre mim&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Engenheiro e agora Desenvolvedor Android em transição de carreira, sempre em busca de novos conhecimentos e experiências. Confira meu perfil no LinkedIn e no GitHub, e sinta-se a vontade para qualquer contato!&lt;/p&gt;

&lt;p&gt;👔 &lt;a href="https://www.linkedin.com/in/vitor-xatara-branco/"&gt;https://www.linkedin.com/in/vitor-xatara-branco/&lt;/a&gt;&lt;br&gt;
💻 &lt;a href="https://github.com/vitorbranco"&gt;https://github.com/vitorbranco&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
