Puedes probar tu aplicación mediante pruebas unitarias o de integración. Las pruebas unitarias solo evalúan la lógica escrita en el código de tu aplicación. Las pruebas de integración evalúan la lógica de tu aplicación, las consultas y escrituras en la base de datos, y las llamadas al backend de tu aplicación, si lo tienes. Las pruebas unitarias se ejecutan en tu equipo de desarrollo mediante la JVM, mientras que las pruebas de integración se ejecutan en un dispositivo Android físico o emulado. Puedes ejecutar pruebas de integración comunicándote con instancias reales de Realm o con el backend de una aplicación mediante las pruebas instrumentadas integradas de Android.
Android utiliza rutas de archivos y nombres de carpetas específicos en proyectos de Android para pruebas unitarias y pruebas instrumentadas:
Tipo de prueba | Ruta de acceso |
|---|---|
Pruebas unitarias |
|
Pruebas instrumentadas |
|
Dado que el SDK utiliza código C++ a través de Android Native para el almacenamiento de datos, las pruebas unitarias requieren simular completamente las interacciones con Realm. Se recomiendan las pruebas de integración para la lógica que requiere una interacción extensa con la base de datos.
Pruebas de integración
Esta sección muestra cómo realizar pruebas de integración en una aplicación que utiliza el SDK de Realm. Abarca los siguientes conceptos en el entorno de pruebas:
Adquirir un contexto de aplicación
ejecutando lógica en un
LooperhiloCómo retrasar la ejecución de una prueba mientras se completan las llamadas a métodos asincrónicos
Las aplicaciones que utilizan Sync o una aplicación backend también requieren (no se cubre aquí):
un backend de aplicación por separado para pruebas, con cuentas de usuario y datos separados
un clúster Atlas separado que contiene solo datos de prueba
Contexto de aplicación
Para inicializar el SDK, deberá proporcionar una aplicación o actividad
contextEsto no está disponible por defecto en las pruebas de integración de Android. Sin embargo, puedes usar la clase ActivityScenario de pruebas integrada de Android para iniciar una actividad en tus pruebas. Puedes usar cualquier actividad de tu aplicación o crear una actividad vacía solo para pruebas. Llama a ActivityScenario.launch() con tu clase de actividad como parámetro para iniciar la actividad simulada.
A continuación, use el método ActivityScenario.onActivity() para ejecutar una lambda en el hilo principal de la actividad simulada. En esta lambda, debe llamar a la función Realm.init() para inicializar el SDK con su actividad como parámetro. Además, debe guardar el parámetro pasado a su lambda (la instancia recién creada de su actividad) para su uso futuro.
Debido a que el onActivity() método se ejecuta en un hilo diferente, debes bloquear la ejecución de tu prueba hasta que se complete esta configuración inicial.
El siguiente ejemplo utiliza un ActivityScenario, una actividad de prueba vacía y un CountDownLatch para demostrar cómo configurar un entorno donde puede probar su aplicación Realm:
AtomicReference<Activity> testActivity = new AtomicReference<Activity>(); ActivityScenario<BasicActivity> scenario = ActivityScenario.launch(BasicActivity.class); // create a latch to force blocking for an async call to initialize realm CountDownLatch setupLatch = new CountDownLatch(1); scenario.onActivity(activity -> { Realm.init(activity); testActivity.set(activity); setupLatch.countDown(); // unblock the latch await }); // block until we have an activity to run tests on try { Assert.assertTrue(setupLatch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e("EXAMPLE", e.getMessage()); }
var testActivity: Activity? = null val scenario: ActivityScenario<BasicActivity>? = ActivityScenario.launch(BasicActivity::class.java) // create a latch to force blocking for an async call to initialize realm val setupLatch = CountDownLatch(1) scenario?.onActivity{ activity: BasicActivity -> Realm.init(activity) testActivity = activity setupLatch.countDown() // unblock the latch await }
Hilo de bucle
Las funciones de dominio, como los objetos en vivo y las notificaciones de cambios, solo funcionan en subprocesos de Looper. Los subprocesos configurados con un Looper objeto pasan eventos a través de un bucle de mensajes coordinado por Looper el. Las funciones de prueba normalmente no tienen un Looper objeto, y configurar uno para que funcione en las pruebas puede ser muy propenso a errores.
En su lugar, puede usar el método Activity.runOnUiThread() de su actividad de prueba para ejecutar la lógica en un hilo que ya tenga Looper configurado un. Combine Activity.runOnUiThread() con un CountDownLatch como se describe en la sección de retardo para evitar que la prueba se complete y salga antes de que se haya ejecutado la lógica. Dentro de la runOnUiThread() llamada, puede interactuar con el SDK como lo haría normalmente en el código de su aplicación:
testActivity.get().runOnUiThread(() -> { // instantiate an app connection String appID = YOUR_APP_ID; // replace this with your test application App ID App app = new App(new AppConfiguration.Builder(appID).build()); // authenticate a user Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, it -> { if (it.isSuccess()) { Log.v("EXAMPLE", "Successfully authenticated."); // open a synced realm SyncConfiguration config = new SyncConfiguration.Builder( app.currentUser(), getRandomPartition()) // replace this with a valid partition .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess( Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // read and write to realm here via transactions testLatch.countDown(); realm.executeTransaction(new Realm.Transaction() { public void execute( Realm realm) { realm.createObjectFromJson(Frog.class, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }"); } }); realm.close(); } public void onError( Throwable exception) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.getLocalizedMessage()); } }); } else { Log.e("EXAMPLE", "Failed login: " + it.getError().getErrorMessage()); } }); });
testActivity?.runOnUiThread { // instantiate an app connection val appID: String = YOUR_APP_ID // replace this with your App ID val app = App(AppConfiguration.Builder(appID).build()) // authenticate a user val credentials = Credentials.anonymous() app.loginAsync(credentials) { if (it.isSuccess) { Log.v("EXAMPLE", "Successfully authenticated.") // open a synced realm val config = SyncConfiguration.Builder( app.currentUser(), getRandomPartition() // replace this with a valid partition ).allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // read and write to realm here via transactions realm.executeTransaction { realm.createObjectFromJson( Frog::class.java, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id:0 }" ) } testLatch.countDown() realm.close() } override fun onError(exception: Throwable) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.localizedMessage) } }) } else { Log.e("EXAMPLE", "Failed login: " + it.error.errorMessage) } } }
Retrasar la ejecución de la prueba mientras se completan las llamadas asincrónicas
Dado que el SDK utiliza llamadas asíncronas para operaciones comunes como consultas de bases de datos, autenticación y llamadas a funciones, las pruebas necesitan una forma de esperar a que se completen dichas llamadas. De lo contrario, las pruebas finalizarán antes de que se ejecuten las llamadas asíncronas (o multihilo). Este ejemplo utiliza CountDownLatch integrado en Java. Siga estos pasos para usar un CountDownLatch en sus propias pruebas:
Crea una instancia de
CountDownLatchcon un recuento de 1.Después de ejecutar la lógica asincrónica que su prueba debe esperar, llame al método
countDown()de esa instanciaCountDownLatch.Cuando necesite esperar lógica asincrónica, agregue un bloque
try/catchque gestione unInterruptedException. En ese bloque, llame al métodoawait()de esa instanciaCountDownLatch.Asigne un intervalo de tiempo de espera y una unidad a
await(), y envuelva la llamada en una aserciónAssert.assertTrue(). Si la lógica tarda demasiado, la llamadaawait()expira, devuelve falso y falla la prueba.
El siguiente ejemplo demuestra el uso de un CountDownLatch para esperar la autenticación y abrir un reino de forma asincrónica en un hilo separado:
CountDownLatch testLatch = new CountDownLatch(1); testActivity.get().runOnUiThread(() -> { // instantiate an app connection String appID = YOUR_APP_ID; // replace this with your test application App ID App app = new App(new AppConfiguration.Builder(appID).build()); // authenticate a user Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, it -> { if (it.isSuccess()) { Log.v("EXAMPLE", "Successfully authenticated."); // open a synced realm SyncConfiguration config = new SyncConfiguration.Builder( app.currentUser(), getRandomPartition()) // replace this with a valid partition .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess( Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // read and write to realm here via transactions testLatch.countDown(); realm.executeTransaction(new Realm.Transaction() { public void execute( Realm realm) { realm.createObjectFromJson(Frog.class, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }"); } }); realm.close(); } public void onError( Throwable exception) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.getLocalizedMessage()); } }); } else { Log.e("EXAMPLE", "Failed login: " + it.getError().getErrorMessage()); } }); }); // block until the async calls in the test succeed or error out try { Assert.assertTrue(testLatch.await(5, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e("EXAMPLE", e.getMessage()); }
val testLatch = CountDownLatch(1) testActivity?.runOnUiThread { // instantiate an app connection val appID: String = YOUR_APP_ID // replace this with your App ID val app = App(AppConfiguration.Builder(appID).build()) // authenticate a user val credentials = Credentials.anonymous() app.loginAsync(credentials) { if (it.isSuccess) { Log.v("EXAMPLE", "Successfully authenticated.") // open a synced realm val config = SyncConfiguration.Builder( app.currentUser(), getRandomPartition() // replace this with a valid partition ).allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // read and write to realm here via transactions realm.executeTransaction { realm.createObjectFromJson( Frog::class.java, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id:0 }" ) } testLatch.countDown() realm.close() } override fun onError(exception: Throwable) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.localizedMessage) } }) } else { Log.e("EXAMPLE", "Failed login: " + it.error.errorMessage) } } } // block until the async calls in the test succeed or error out try { Assert.assertTrue(testLatch.await(5, TimeUnit.SECONDS)) } catch (e: InterruptedException) { Log.e("EXAMPLE", e.stackTraceToString()) }
Pruebas de backend
Las aplicaciones que utilizan un backend de App no deben conectarse al backend de producción para fines de pruebas, por las siguientes razones:
debes mantener siempre separados a los usuarios de prueba y a los usuarios de producción por motivos de seguridad y privacidad
Las pruebas a menudo requieren un estado inicial limpio, por lo que hay una buena posibilidad de que sus pruebas incluyan un método de configuración o desmontaje que elimine todos los usuarios o grandes fragmentos de datos.
Puede utilizar entornos para administrar aplicaciones independientes para pruebas y producción.
Prueba del clúster Atlas
Las aplicaciones que usan consultas Sync o MongoDB pueden leer, escribir, actualizar o eliminar datos almacenados en clústeres Atlas conectados. Por seguridad, no se recomienda almacenar datos de producción y de prueba en el mismo clúster. Además, las pruebas pueden requerir cambios de esquema antes de que estos se gestionen correctamente en la aplicación de producción. Por lo tanto, se recomienda usar un clúster Atlas independiente al probar la aplicación.
Ejemplo completo
El siguiente ejemplo muestra un ejemplo completo instrumentado de Junit androidTest ejecutando Realm en pruebas de integración:
package com.mongodb.realm.examples.java; import android.app.Activity; import android.util.Log; import androidx.annotation.NonNull; import androidx.test.core.app.ActivityScenario; import com.mongodb.realm.examples.BasicActivity; import com.mongodb.realm.examples.model.kotlin.Frog; import org.junit.Assert; import org.junit.Test; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import io.realm.Realm; import io.realm.mongodb.App; import io.realm.mongodb.AppConfiguration; import io.realm.mongodb.Credentials; import io.realm.mongodb.sync.SyncConfiguration; import static com.mongodb.realm.examples.RealmTestKt.YOUR_APP_ID; import static com.mongodb.realm.examples.RealmTestKt.getRandomPartition; public class TestTest { public void testTesting() { AtomicReference<Activity> testActivity = new AtomicReference<Activity>(); ActivityScenario<BasicActivity> scenario = ActivityScenario.launch(BasicActivity.class); // create a latch to force blocking for an async call to initialize realm CountDownLatch setupLatch = new CountDownLatch(1); scenario.onActivity(activity -> { Realm.init(activity); testActivity.set(activity); setupLatch.countDown(); // unblock the latch await }); // block until we have an activity to run tests on try { Assert.assertTrue(setupLatch.await(1, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e("EXAMPLE", e.getMessage()); } CountDownLatch testLatch = new CountDownLatch(1); testActivity.get().runOnUiThread(() -> { // instantiate an app connection String appID = YOUR_APP_ID; // replace this with your test application App ID App app = new App(new AppConfiguration.Builder(appID).build()); // authenticate a user Credentials credentials = Credentials.anonymous(); app.loginAsync(credentials, it -> { if (it.isSuccess()) { Log.v("EXAMPLE", "Successfully authenticated."); // open a synced realm SyncConfiguration config = new SyncConfiguration.Builder( app.currentUser(), getRandomPartition()) // replace this with a valid partition .allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build(); Realm.getInstanceAsync(config, new Realm.Callback() { public void onSuccess( Realm realm) { Log.v("EXAMPLE", "Successfully opened a realm."); // read and write to realm here via transactions testLatch.countDown(); realm.executeTransaction(new Realm.Transaction() { public void execute( Realm realm) { realm.createObjectFromJson(Frog.class, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }"); } }); realm.close(); } public void onError( Throwable exception) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.getLocalizedMessage()); } }); } else { Log.e("EXAMPLE", "Failed login: " + it.getError().getErrorMessage()); } }); }); // block until the async calls in the test succeed or error out try { Assert.assertTrue(testLatch.await(5, TimeUnit.SECONDS)); } catch (InterruptedException e) { Log.e("EXAMPLE", e.getMessage()); } } }
package com.mongodb.realm.examples.kotlin import android.app.Activity import android.util.Log import androidx.test.core.app.ActivityScenario import com.mongodb.realm.examples.BasicActivity import com.mongodb.realm.examples.YOUR_APP_ID import com.mongodb.realm.examples.getRandomPartition import com.mongodb.realm.examples.model.kotlin.Frog import io.realm.Realm import io.realm.mongodb.App import io.realm.mongodb.AppConfiguration import io.realm.mongodb.Credentials import io.realm.mongodb.sync.SyncConfiguration import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import org.junit.Assert import org.junit.Test class TestTest { fun testTesting() { var testActivity: Activity? = null val scenario: ActivityScenario<BasicActivity>? = ActivityScenario.launch(BasicActivity::class.java) // create a latch to force blocking for an async call to initialize realm val setupLatch = CountDownLatch(1) scenario?.onActivity{ activity: BasicActivity -> Realm.init(activity) testActivity = activity setupLatch.countDown() // unblock the latch await } // block until we have an activity to run tests on try { Assert.assertTrue(setupLatch.await(1, TimeUnit.SECONDS)) } catch (e: InterruptedException) { Log.e("EXAMPLE", e.stackTraceToString()) } val testLatch = CountDownLatch(1) testActivity?.runOnUiThread { // instantiate an app connection val appID: String = YOUR_APP_ID // replace this with your App ID val app = App(AppConfiguration.Builder(appID).build()) // authenticate a user val credentials = Credentials.anonymous() app.loginAsync(credentials) { if (it.isSuccess) { Log.v("EXAMPLE", "Successfully authenticated.") // open a synced realm val config = SyncConfiguration.Builder( app.currentUser(), getRandomPartition() // replace this with a valid partition ).allowQueriesOnUiThread(true) .allowWritesOnUiThread(true) .build() Realm.getInstanceAsync(config, object : Realm.Callback() { override fun onSuccess(realm: Realm) { Log.v("EXAMPLE", "Successfully opened a realm.") // read and write to realm here via transactions realm.executeTransaction { realm.createObjectFromJson( Frog::class.java, "{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id:0 }" ) } testLatch.countDown() realm.close() } override fun onError(exception: Throwable) { Log.e("EXAMPLE", "Failed to open the realm: " + exception.localizedMessage) } }) } else { Log.e("EXAMPLE", "Failed login: " + it.error.errorMessage) } } } // block until the async calls in the test succeed or error out try { Assert.assertTrue(testLatch.await(5, TimeUnit.SECONDS)) } catch (e: InterruptedException) { Log.e("EXAMPLE", e.stackTraceToString()) } } }
Tip
Consulta la aplicación de ejemplos de documentación de Realm para ver un ejemplo de prueba de integración del SDK localmente y con un backend en vivo.
Pruebas unitarias
Para realizar pruebas unitarias en aplicaciones Realm que utilizan Realm, debe simular Realm (y el backend de su aplicación, si lo utiliza). Utilice las siguientes bibliotecas para simular la funcionalidad del SDK:
Para que estas bibliotecas estén disponibles para pruebas unitarias en su proyecto de Android, agregue lo siguiente al bloque dependencies del archivo build.gradle de su aplicación:
testImplementation "org.robolectric:robolectric:4.1" testImplementation "org.mockito:mockito-core:3.3.3" testImplementation "org.powermock:powermock-module-junit4:2.0.9" testImplementation "org.powermock:powermock-module-junit4-rule:2.0.9" testImplementation "org.powermock:powermock-api-mockito2:2.0.9" testImplementation "org.powermock:powermock-classloading-xstream:2.0.9"
Nota
Compatibilidad de versiones
Para simular el SDK en pruebas unitarias, se requieren Robolectric, Mockito y Powermock, ya que el SDK utiliza llamadas a métodos de C++ nativo de Android para interactuar con Realm. Dado que los frameworks necesarios para anular estas llamadas a métodos pueden ser delicados, se recomienda usar las versiones mencionadas anteriormente para garantizar el éxito de la simulación. Algunas actualizaciones recientes (en particular, la versión 4.2+ de Robolectric) pueden interrumpir la compilación de pruebas unitarias con el SDK.
Para configurar sus pruebas unitarias para usar Robolectric, PowerMock y Mockito con el SDK, agregue las siguientes anotaciones a cada clase de prueba unitaria que simule el SDK:
Luego, inicie Powermock globalmente en la clase de prueba:
// bootstrap powermock public PowerMockRule rule = new PowerMockRule();
// bootstrap powermock var rule = PowerMockRule()
A continuación, simule los componentes del SDK que podrían consultar código C++ nativo para no alcanzar las limitaciones del entorno de prueba:
// set up realm SDK components to be mocked. The order of these matters mockStatic(RealmCore.class); mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); Realm.init(RuntimeEnvironment.application); // boilerplate to mock realm components -- this prevents us from hitting any // native code doNothing().when(RealmCore.class); RealmCore.loadLibrary(any(Context.class));
// set up realm SDK components to be mocked. The order of these matters PowerMockito.mockStatic(RealmCore::class.java) PowerMockito.mockStatic(RealmLog::class.java) PowerMockito.mockStatic(Realm::class.java) PowerMockito.mockStatic(RealmConfiguration::class.java) Realm.init(RuntimeEnvironment.application) PowerMockito.doNothing().`when`(RealmCore::class.java) RealmCore.loadLibrary(ArgumentMatchers.any(Context::class.java))
Una vez completada la configuración necesaria para la simulación, puede empezar a simular componentes y configurar el comportamiento de sus pruebas. También puede configurar PowerMockito para que devuelva objetos específicos al instanciar nuevos objetos de un tipo, de modo que incluso el código que haga referencia al dominio predeterminado de su aplicación no afecte negativamente a sus pruebas.
// create the mocked realm final Realm mockRealm = mock(Realm.class); final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class); // use this mock realm config for all new realm configurations whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); // use this mock realm for all new default realms when(Realm.getDefaultInstance()).thenReturn(mockRealm);
// create the mocked realm val mockRealm = PowerMockito.mock(Realm::class.java) val mockRealmConfig = PowerMockito.mock( RealmConfiguration::class.java ) // use this mock realm config for all new realm configurations PowerMockito.whenNew(RealmConfiguration::class.java).withAnyArguments() .thenReturn(mockRealmConfig) // use this mock realm for all new default realms PowerMockito.`when`(Realm.getDefaultInstance()).thenReturn(mockRealm)
Después de simular un reino, deberá configurar los datos para sus casos de prueba. Consulte el ejemplo completo a continuación para ver algunos ejemplos de cómo proporcionar datos de prueba en pruebas unitarias.
Ejemplo completo
El siguiente ejemplo muestra un ejemplo completo de JUnit test simulando Realm en pruebas unitarias. Este ejemplo prueba una actividad que realiza algunas operaciones básicas de Realm. Las pruebas utilizan la simulación para simular dichas operaciones cuando la actividad se inicia durante una prueba unitaria:
package com.mongodb.realm.examples.java; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; import android.os.AsyncTask; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; import com.mongodb.realm.examples.R; import com.mongodb.realm.examples.model.java.Cat; import io.realm.Realm; import io.realm.RealmResults; public class UnitTestActivity extends AppCompatActivity { public static final String TAG = UnitTestActivity.class.getName(); private LinearLayout rootLayout = null; private Realm realm; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Realm.init(getApplicationContext()); setContentView(R.layout.activity_unit_test); rootLayout = findViewById(R.id.container); rootLayout.removeAllViews(); // open the default Realm for the UI thread. realm = Realm.getDefaultInstance(); // clean up from previous run cleanUp(); // small operation that is ok to run on the main thread basicCRUD(realm); // more complex operations can be executed on another thread. AsyncTask<Void, Void, String> foo = new AsyncTask<Void, Void, String>() { protected String doInBackground(Void... voids) { String info = ""; info += complexQuery(); return info; } protected void onPostExecute(String result) { showStatus(result); } }; foo.execute(); findViewById(R.id.clean_up).setOnClickListener(view -> { view.setEnabled(false); Log.d("TAG", "clean up"); cleanUp(); view.setEnabled(true); }); } private void cleanUp() { // delete all cats realm.executeTransaction(r -> r.delete(Cat.class)); } public void onDestroy() { super.onDestroy(); realm.close(); // remember to close realm when done. } private void showStatus(String txt) { Log.i(TAG, txt); TextView tv = new TextView(this); tv.setText(txt); rootLayout.addView(tv); } private void basicCRUD(Realm realm) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations..."); // all writes must be wrapped in a transaction to facilitate safe multi threading realm.executeTransaction(r -> { // add a cat Cat cat = r.createObject(Cat.class); cat.setName("John Young"); }); // find the first cat (no query conditions) and read a field final Cat cat = realm.where(Cat.class).findFirst(); showStatus(cat.getName()); // update cat in a transaction realm.executeTransaction(r -> { cat.setName("John Senior"); }); showStatus(cat.getName()); // add two more cats realm.executeTransaction(r -> { Cat jane = r.createObject(Cat.class); jane.setName("Jane"); Cat doug = r.createObject(Cat.class); doug.setName("Robert"); }); RealmResults<Cat> cats = realm.where(Cat.class).findAll(); showStatus(String.format("Found %s cats", cats.size())); for (Cat p : cats) { showStatus("Found " + p.getName()); } } private String complexQuery() { String status = "\n\nPerforming complex Query operation..."; Realm realm = Realm.getDefaultInstance(); status += "\nNumber of cats in the DB: " + realm.where(Cat.class).count(); // find all cats where name begins with "J". RealmResults<Cat> results = realm.where(Cat.class) .beginsWith("name", "J") .findAll(); status += "\nNumber of cats whose name begins with 'J': " + results.size(); realm.close(); return status; } }
import android.content.Context; import com.mongodb.realm.examples.java.UnitTestActivity; import com.mongodb.realm.examples.model.java.Cat; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PowerMockIgnore; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; import org.powermock.modules.junit4.rule.PowerMockRule; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.Arrays; import java.util.List; import io.realm.Realm; import io.realm.RealmConfiguration; import io.realm.RealmObject; import io.realm.RealmQuery; import io.realm.RealmResults; import io.realm.internal.RealmCore; import io.realm.log.RealmLog; import com.mongodb.realm.examples.R; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doCallRealMethod; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.powermock.api.mockito.PowerMockito.doNothing; import static org.powermock.api.mockito.PowerMockito.mock; import static org.powermock.api.mockito.PowerMockito.mockStatic; import static org.powermock.api.mockito.PowerMockito.when; import static org.powermock.api.mockito.PowerMockito.whenNew; public class TestTest { // bootstrap powermock public PowerMockRule rule = new PowerMockRule(); // mocked realm SDK components for tests private Realm mockRealm; private RealmResults<Cat> cats; public void setup() throws Exception { // set up realm SDK components to be mocked. The order of these matters mockStatic(RealmCore.class); mockStatic(RealmLog.class); mockStatic(Realm.class); mockStatic(RealmConfiguration.class); Realm.init(RuntimeEnvironment.application); // boilerplate to mock realm components -- this prevents us from hitting any // native code doNothing().when(RealmCore.class); RealmCore.loadLibrary(any(Context.class)); // create the mocked realm final Realm mockRealm = mock(Realm.class); final RealmConfiguration mockRealmConfig = mock(RealmConfiguration.class); // use this mock realm config for all new realm configurations whenNew(RealmConfiguration.class).withAnyArguments().thenReturn(mockRealmConfig); // use this mock realm for all new default realms when(Realm.getDefaultInstance()).thenReturn(mockRealm); // any time we ask Realm to create a Cat, return a new instance. when(mockRealm.createObject(Cat.class)).thenReturn(new Cat()); // set up test data Cat p1 = new Cat(); p1.setName("Enoch"); Cat p2 = new Cat(); p2.setName("Quincy Endicott"); Cat p3 = new Cat(); p3.setName("Sara"); Cat p4 = new Cat(); p4.setName("Jimmy Brown"); List<Cat> catList = Arrays.asList(p1, p2, p3, p4); // create a mocked RealmQuery RealmQuery<Cat> catQuery = mockRealmQuery(); // when the RealmQuery performs findFirst, return the first record in the list. when(catQuery.findFirst()).thenReturn(catList.get(0)); // when the where clause is called on the Realm, return the mock query. when(mockRealm.where(Cat.class)).thenReturn(catQuery); // when the RealmQuery is filtered on any string and any integer, return the query when(catQuery.equalTo(anyString(), anyInt())).thenReturn(catQuery); // when a between query is performed with any string as the field and any int as the // value, then return the catQuery itself when(catQuery.between(anyString(), anyInt(), anyInt())).thenReturn(catQuery); // When a beginsWith clause is performed with any string field and any string value // return the same cat query when(catQuery.beginsWith(anyString(), anyString())).thenReturn(catQuery); // RealmResults is final, must mock static and also place this in the PrepareForTest // annotation array. mockStatic(RealmResults.class); // create a mock RealmResults RealmResults<Cat> cats = mockRealmResults(); // the for(...) loop in Java needs an iterator, so we're giving it one that has items, // since the mock RealmResults does not provide an implementation. Therefore, any time // anyone asks for the RealmResults Iterator, give them a functioning iterator from the // ArrayList of Cats we created above. This will allow the loop to execute. when(cats.iterator()).thenReturn(catList.iterator()); // Return the size of the mock list. when(cats.size()).thenReturn(catList.size()); // when we ask Realm for all of the Cat instances, return the mock RealmResults when(mockRealm.where(Cat.class).findAll()).thenReturn(cats); // when we ask the RealmQuery for all of the Cat objects, return the mock RealmResults when(catQuery.findAll()).thenReturn(cats); this.mockRealm = mockRealm; this.cats = cats; } public void shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { doCallRealMethod().when(mockRealm) .executeTransaction(any(Realm.Transaction.class)); // create test activity -- onCreate method calls methods that // query/write to realm UnitTestActivity activity = Robolectric .buildActivity(UnitTestActivity.class) .create() .start() .resume() .visible() .get(); // click the clean up button activity.findViewById(R.id.clean_up).performClick(); // verify that we queried for Cat instances five times in this run // (2 in basicCrud(), 2 in complexQuery() and 1 in the button click) verify(mockRealm, times(5)).where(Cat.class); // verify that the delete method was called. We also call delete at // the start of the activity to ensure we start with a clean db. verify(mockRealm, times(2)).delete(Cat.class); // call the destroy method so we can verify that the .close() method // was called (below) activity.onDestroy(); // verify that the realm got closed 2 separate times. Once in the // AsyncTask, once in onDestroy verify(mockRealm, times(2)).close(); } private <T extends RealmObject> RealmQuery<T> mockRealmQuery() { return mock(RealmQuery.class); } private <T extends RealmObject> RealmResults<T> mockRealmResults() { return mock(RealmResults.class); } }
package com.mongodb.realm.examples.kotlin import android.os.AsyncTask import android.os.Bundle import android.util.Log import android.view.View import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.mongodb.realm.examples.R import com.mongodb.realm.examples.model.java.Cat import io.realm.Realm class UnitTestActivity : AppCompatActivity() { private var rootLayout: LinearLayout? = null private var realm: Realm? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Realm.init(applicationContext) setContentView(R.layout.activity_unit_test) rootLayout = findViewById(R.id.container) rootLayout!!.removeAllViews() // open the default Realm for the UI thread. realm = Realm.getDefaultInstance() // clean up from previous run cleanUp() // small operation that is ok to run on the main thread basicCRUD(realm) // more complex operations can be executed on another thread. val foo: AsyncTask<Void?, Void?, String> = object : AsyncTask<Void?, Void?, String>() { protected override fun doInBackground(vararg params: Void?): String? { var info = "" info += complexQuery() return info } override fun onPostExecute(result: String) { showStatus(result) } } foo.execute() findViewById<View>(R.id.clean_up).setOnClickListener { view: View -> view.isEnabled = false Log.d("TAG", "clean up") cleanUp() view.isEnabled = true } } private fun cleanUp() { // delete all cats realm!!.executeTransaction { r: Realm -> r.delete(Cat::class.java) } } public override fun onDestroy() { super.onDestroy() realm!!.close() // remember to close realm when done. } private fun showStatus(txt: String) { Log.i(TAG, txt) val tv = TextView(this) tv.text = txt rootLayout!!.addView(tv) } private fun basicCRUD(realm: Realm?) { showStatus("Perform basic Create/Read/Update/Delete (CRUD) operations...") // all writes must be wrapped in a transaction to facilitate safe multi threading realm!!.executeTransaction { r: Realm -> // add a cat val cat = r.createObject(Cat::class.java) cat.name = "John Young" } // find the first cat (no query conditions) and read a field val cat = realm.where(Cat::class.java).findFirst() showStatus(cat!!.name) // update cat in a transaction realm.executeTransaction { r: Realm? -> cat.name = "John Senior" } showStatus(cat.name) // add two more cats realm.executeTransaction { r: Realm -> val jane = r.createObject(Cat::class.java) jane.name = "Jane" val doug = r.createObject(Cat::class.java) doug.name = "Robert" } val cats = realm.where(Cat::class.java).findAll() showStatus(String.format("Found %s cats", cats.size)) for (p in cats) { showStatus("Found " + p.name) } } private fun complexQuery(): String { var status = "\n\nPerforming complex Query operation..." val realm = Realm.getDefaultInstance() status += """ Number of cats in the DB: ${realm.where(Cat::class.java).count()} """.trimIndent() // find all cats where name begins with "J". val results = realm.where(Cat::class.java) .beginsWith("name", "J") .findAll() status += """ Number of cats whose name begins with 'J': ${results.size} """.trimIndent() realm.close() return status } companion object { val TAG = UnitTestActivity::class.java.name } }
import android.content.Context import android.view.View import com.mongodb.realm.examples.R import com.mongodb.realm.examples.kotlin.UnitTestActivity import com.mongodb.realm.examples.model.java.Cat import io.realm.Realm import io.realm.RealmConfiguration import io.realm.RealmObject import io.realm.RealmQuery import io.realm.RealmResults import io.realm.internal.RealmCore import io.realm.log.RealmLog import java.lang.Exception import java.util.* import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mockito import org.powermock.api.mockito.PowerMockito import org.powermock.core.classloader.annotations.PowerMockIgnore import org.powermock.core.classloader.annotations.PrepareForTest import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor import org.powermock.modules.junit4.rule.PowerMockRule import org.robolectric.Robolectric import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config class TestTest { // bootstrap powermock var rule = PowerMockRule() // mocked realm SDK components for tests private var mockRealm: Realm? = null private var cats: RealmResults<Cat>? = null fun setup() { // set up realm SDK components to be mocked. The order of these matters PowerMockito.mockStatic(RealmCore::class.java) PowerMockito.mockStatic(RealmLog::class.java) PowerMockito.mockStatic(Realm::class.java) PowerMockito.mockStatic(RealmConfiguration::class.java) Realm.init(RuntimeEnvironment.application) PowerMockito.doNothing().`when`(RealmCore::class.java) RealmCore.loadLibrary(ArgumentMatchers.any(Context::class.java)) // create the mocked realm val mockRealm = PowerMockito.mock(Realm::class.java) val mockRealmConfig = PowerMockito.mock( RealmConfiguration::class.java ) // use this mock realm config for all new realm configurations PowerMockito.whenNew(RealmConfiguration::class.java).withAnyArguments() .thenReturn(mockRealmConfig) // use this mock realm for all new default realms PowerMockito.`when`(Realm.getDefaultInstance()).thenReturn(mockRealm) // any time we ask Realm to create a Cat, return a new instance. PowerMockito.`when`(mockRealm.createObject(Cat::class.java)).thenReturn(Cat()) // set up test data val p1 = Cat() p1.name = "Enoch" val p2 = Cat() p2.name = "Quincy Endicott" val p3 = Cat() p3.name = "Sara" val p4 = Cat() p4.name = "Jimmy Brown" val catList = Arrays.asList(p1, p2, p3, p4) // create a mocked RealmQuery val catQuery = mockRealmQuery<Cat>() // when the RealmQuery performs findFirst, return the first record in the list. PowerMockito.`when`(catQuery!!.findFirst()).thenReturn(catList[0]) // when the where clause is called on the Realm, return the mock query. PowerMockito.`when`(mockRealm.where(Cat::class.java)).thenReturn(catQuery) // when the RealmQuery is filtered on any string and any integer, return the query PowerMockito.`when`( catQuery.equalTo( ArgumentMatchers.anyString(), ArgumentMatchers.anyInt() ) ).thenReturn(catQuery) // when a between query is performed with any string as the field and any int as the // value, then return the catQuery itself PowerMockito.`when`( catQuery.between( ArgumentMatchers.anyString(), ArgumentMatchers.anyInt(), ArgumentMatchers.anyInt() ) ).thenReturn(catQuery) // When a beginsWith clause is performed with any string field and any string value // return the same cat query PowerMockito.`when`( catQuery.beginsWith( ArgumentMatchers.anyString(), ArgumentMatchers.anyString() ) ).thenReturn(catQuery) // RealmResults is final, must mock static and also place this in the PrepareForTest // annotation array. PowerMockito.mockStatic(RealmResults::class.java) // create a mock RealmResults val cats = mockRealmResults<Cat>() // the for(...) loop in Java needs an iterator, so we're giving it one that has items, // since the mock RealmResults does not provide an implementation. Therefore, any time // anyone asks for the RealmResults Iterator, give them a functioning iterator from the // ArrayList of Cats we created above. This will allow the loop to execute. PowerMockito.`when`<Iterator<Cat>>(cats!!.iterator()).thenReturn(catList.iterator()) // Return the size of the mock list. PowerMockito.`when`(cats.size).thenReturn(catList.size) // when we ask Realm for all of the Cat instances, return the mock RealmResults PowerMockito.`when`(mockRealm.where(Cat::class.java).findAll()).thenReturn(cats) // when we ask the RealmQuery for all of the Cat objects, return the mock RealmResults PowerMockito.`when`(catQuery.findAll()).thenReturn(cats) this.mockRealm = mockRealm this.cats = cats } fun shouldBeAbleToAccessActivityAndVerifyRealmInteractions() { Mockito.doCallRealMethod().`when`(mockRealm)!! .executeTransaction(ArgumentMatchers.any(Realm.Transaction::class.java)) // create test activity -- onCreate method calls methods that // query/write to realm val activity = Robolectric .buildActivity(UnitTestActivity::class.java) .create() .start() .resume() .visible() .get() // click the clean up button activity.findViewById<View>(R.id.clean_up).performClick() // verify that we queried for Cat instances five times in this run // (2 in basicCrud(), 2 in complexQuery() and 1 in the button click) Mockito.verify(mockRealm, Mockito.times(5))!!.where(Cat::class.java) // verify that the delete method was called. We also call delete at // the start of the activity to ensure we start with a clean db. Mockito.verify(mockRealm, Mockito.times(2))!!.delete(Cat::class.java) // call the destroy method so we can verify that the .close() method // was called (below) activity.onDestroy() // verify that the realm got closed 2 separate times. Once in the // AsyncTask, once in onDestroy Mockito.verify(mockRealm, Mockito.times(2))!!.close() } private fun <T : RealmObject?> mockRealmQuery(): RealmQuery<T>? { return PowerMockito.mock(RealmQuery::class.java) as RealmQuery<T> } private fun <T : RealmObject?> mockRealmResults(): RealmResults<T>? { return PowerMockito.mock(RealmResults::class.java) as RealmResults<T> } }
Tip
Consulte la aplicación de ejemplo de prueba unitaria para ver un ejemplo de prueba unitaria de una aplicación que utiliza Realm.