Cómo simular una base de datos reactiva en Room con Fakes
Antonio Leiva

En el desarrollo de aplicaciones móviles es muy común utilizar bases de datos para almacenar y gestionar la información que se utiliza en la aplicación. En el caso de Android, una de las opciones más populares es Room, una librería de persistencia de datos que proporciona una capa de abstracción sobre SQLite.

Una de las ventajas de Room es que ofrece soporte para el uso de datos reactivos, lo que permite a los desarrolladores trabajar con flujos de datos en lugar de con consultas síncronas. Esto hace que sea más sencillo y eficiente gestionar la información en tiempo real, ya que los cambios en la base de datos se reflejan de forma automática en los flujos de datos.

Sin embargo, durante el proceso de testing es conveniente poder simular la base de datos y no depender de una base de datos real. En este artículo veremos cómo podemos crear una clase «fake» que simule una base de datos reactiva utilizando Room y que podamos utilizar durante los tests.

Definición de la interfaz del DataSource

Lo primero que debemos hacer es definir una interfaz que represente nuestra fuente de datos local. En este caso, vamos a suponer que tenemos una aplicación de películas y que queremos almacenar una lista de películas en la base de datos. La interfaz podría quedar de la siguiente forma:

interface MovieLocalDataSource {
    val movies: Flow<List<Movie>>

    suspend fun isEmpty(): Boolean
    fun findById(id: Int): Flow<Movie>
    suspend fun save(movies: List<Movie>): Error?
}

Como se puede ver, la interfaz cuenta con una propiedad movies que es un Flow de listas de películas, y tres métodos:

  • isEmpty: un método suspendido que nos permite comprobar si la base de datos está vacía.
  • findById: un método que nos permite buscar una película por su identificador y que devuelve un Flow de la película encontrada.
  • save: un método suspendido que nos permite guardar una lista de películas en la base de datos y que devuelve un Error en caso de que se produzca algún problema.

Implementación del DataSource con Room

Ahora vamos a implementar una clase que se encargue de conectarse a una base de datos Room y que implemente la interfaz MovieLocalDataSource.

Vamos a suponer que tenemos una entidad MovieEntity que representa a las películas en la base de datos y una clase MovieDao que proporciona los métodos necesarios para realizar operaciones en la base de datos (como obtener todas las películas, insertar películas, etc).

Con esto en mente, la clase MovieRoomDataSource podría quedar de la siguiente forma:

class MovieRoomDataSource @Inject constructor(private val movieDao: MovieDao) : MovieLocalDataSource {

    override val movies: Flow<List<Movie>> = movieDao.getAll().map { it.toDomainModel() }

    override suspend fun isEmpty(): Boolean = movieDao.movieCount() == 0

    override fun findById(id: Int): Flow<Movie> = movieDao.findById(id).map { it.toDomainModel() }

    override suspend fun save(movies: List<Movie>): Error? = tryCall {
        movieDao.insertMovies(movies.fromDomainModel())
    }.fold(
        ifLeft = { it },
        ifRight = { null }
    )
}

Como se puede ver, en la implementación de la interfaz MovieLocalDataSource se hace uso de la clase MovieDao para acceder a la base de datos y realizar las operaciones correspondientes. Por ejemplo, para obtener todas las películas se utiliza el método getAll de MovieDao y para insertar películas se utiliza el método insertMovies.

Implementación del DataSource Fake

Ahora que tenemos la implementación que se conecta a una base de datos Room, vamos a crear la clase «fake» que simulará la base de datos en memoria. Esta clase se llamará FakeLocalDataSource y tendrá la siguiente implementación:

class FakeLocalDataSource : MovieLocalDataSource {

    val inMemoryMovies = MutableStateFlow<List<Movie>>(emptyList())

    override val movies = inMemoryMovies

    override suspend fun isEmpty() = movies.value.isEmpty()

    override fun findById(id: Int): Flow<Movie> = movies
        .map { it.first { movie -> movie.id == id } }

    override suspend fun save(movies: List<Movie>): Error? {
        inMemoryMovies.value = movies
        return null
    }
}

En esta clase se utiliza una propiedad llamada inMemoryMovies que es un MutableStateFlow con una lista de películas vacía. Esta propiedad es expuesta como el Flow de películas de la interfaz a través de la propiedad movies.

Cuando se desee añadir películas a la base de datos en memoria, se puede hacer a través del método save de FakeLocalDataSource, que recibe una lista de películas y las asigna a inMemoryMovies. De esta forma, todos los observadores del Flow movies recibirán los nuevos datos.

Conclusión

Es importante destacar que, al implementar la interfaz MovieLocalDataSource, ambas clases deben exponer un Flow de películas y un método suspend para comprobar si la base de datos está vacía, además de métodos para buscar una película por su identificador y guardar una lista de películas.

En el caso de MovieRoomDataSource, estos métodos hacen uso de la clase MovieDao para acceder a la base de datos Room y realizar las operaciones correspondientes.

En cambio, en FakeLocalDataSource, estos métodos se implementan utilizando la propiedad inMemoryMovies y sin hacer uso de una base de datos.

Quizá también te interese…

Cómo crear un backend en Kotlin usando Ktor

Cómo crear un backend en Kotlin usando Ktor

Ktor es un framework de servidor web ligero y rápido para Kotlin, desarrollado por JetBrains. Es ideal para crear aplicaciones web y servicios RESTful, y es muy fácil de usar y configurar. En este artículo, vamos a ver cómo crear un backend para una aplicación de...

Flows de Kotlin para implementar búsquedas en tiempo real

Flows de Kotlin para implementar búsquedas en tiempo real

En Android, los Flows de Kotlin son una manera de representar secuencias de datos asincrónicas que emiten valores de forma continua. Estos Flows pueden ser útiles en situaciones en las que deseamos escuchar eventos y procesar los resultados de forma asíncrona, como en...

Kata del TicTacToe en Kotlin

Kata del TicTacToe en Kotlin

Escribe el código para representar una entidad que almacene el tablero de juego del 3 en raya, y que además tenga: Un método move() con 2 parámetros, fila y columna, que permita añadir un movimiento al tablero Un método findWinner(), que devuelva el ganador (X, Y o...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *