DEV Community

Cover image for Como testar um LiveData?
vitorbranco for Comunidade Dev Space

Posted on

Como testar um LiveData?

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.

O que é LiveData?

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.
Algumas vantagens do uso do LiveData, além das atualizações automáticas da UI, são:

  • 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.
  • 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.
  • 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.

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.

Como testar um LiveData?

Para testar um LiveData é muito importante lembrar que o LiveData é uma classe observável, ou seja, depende de um Observer 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.

Criando um ViewModel

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

class AlunoViewModel: ViewModel() {
    private val _alunoLiveData = MutableLiveData<String>()
    val alunoLiveData: LiveData<String> = _alunoLiveData

    fun setNomeDoAluno(nome: String) {
        _alunoLiveData.value = nome
    }
}
Enter fullscreen mode Exit fullscreen mode

Criando o teste

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

Escrevemos então a função isLiveDataWorking() com a notação @Test (da biblioteca do JUnit), e nela utilizamos a função setNomeDoAluno() 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:

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

    @Test
    fun `isLiveDataWorking`() {
        underTest.setNomeDoAluno("Vitor")
        assertEquals(underTest.alunoLiveData.value, "Vitor")
    }
}
Enter fullscreen mode Exit fullscreen mode

Ao rodar o teste acima, de cara temos o nosso primeiro erro! Não se assuste, esse erro é extremamente comum.

Imagem com o erro 'Method getMainLooper in android.os.Looper not mocked.'

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.

Para resolver esse problema, nós usamos a regra InstantTaskExecutorRule, que veremos a seguir.

InstantTaskExecutorRule

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.

Para utilizá-lo, basta adicionar a regra da seguinte maneira:

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")
    }
}
Enter fullscreen mode Exit fullscreen mode

Agora sim, nosso teste estará funcionando.
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:

class AlunoViewModel: ViewModel() {
    private val _alunoLiveData = MutableLiveData<String>()

    // 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
    }
}
Enter fullscreen mode Exit fullscreen mode

Ao realizar o mesmo procedimento do teste que fizemos com o alunoLiveData, alterando o nosso assertEquals, temos outro erro:

assertEquals(underTest.numeroDeCaracteresLiveData.value, 5)

Imagem do teste falho: nosso resultado esperado não bateu com o resultado obtido.

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 observado!

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

Vamos resolver esse problema colocando um Test Helper.

Criando um Test Helper

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

É 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 Observer (coisa que o observeForever() não faz).

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.

Crie um novo arquivo (do tipo File mesmo) e adicione o código fornecido pela Google para ajudar nos testes de LiveData:

fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        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
}
Enter fullscreen mode Exit fullscreen mode

Não se esqueça de adicionar corretamente todas as dependências necessárias nesse arquivo:

import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
Enter fullscreen mode Exit fullscreen mode

A função getOrAwaitValue() 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:

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

Ao rodar o teste dessa maneira, temos então o nosso tão almejado "Tests passed"!

Resultado exibindo "Tests passed"

Sobre mim

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!

👔 https://www.linkedin.com/in/vitor-xatara-branco/
💻 https://github.com/vitorbranco

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay