Menu Docs
Página inicial do Docs
/ /
Atlas Device SDKs
/ /

Teste - SDK Java

Nesta página

  • Testes de integração
  • Contexto do aplicativo
  • Linha Looper
  • Atraso na execução do teste enquanto as chamadas assíncronas são concluídas
  • Testando o backend
  • Testando o Atlas Cluster
  • Exemplo completo
  • Testes unitários
  • Exemplo completo

Você pode testar seu aplicativo usando testes de unidade ou testes de integração. Os testes de unidade avaliam apenas a lógica escrita no código do aplicativo. Os testes de integração avaliam a lógica do aplicativo, as queries e gravações do banco de dados e as chamadas para o backend do aplicativo, se você tiver um. Os testes de unidade são executados em sua máquina de desenvolvimento usando a JVM, enquanto os testes de integração são executados em um dispositivo Android físico ou emulado. Você pode executar testes de integração comunicando-se com instâncias reais do Realm ou um backend de aplicativo usando os testes instrumentados internos do Android.

O Android usa caminhos de arquivo e nomes de pastas específicos em projetos Android para testes de unidade e testes instrumentados:

Tipo de teste
Caminho
Testes unitários
/app/src/test
Testes instrumentados
/app/src/androidTest

Como o SDK usa código C++ via Android Native para armazenamento de dados, o teste de unidade exige que você simule totalmente as interações com o Realm. Prefira testes de integração para lógica que exija ampla interação com o banco de dados.

Esta seção mostra como testar a integração de um aplicativo que usa o Realm SDK. Ele abrange os seguintes conceitos no ambiente de teste:

  • adquirindo um contexto de aplicativo

  • executando lógica em um tópico Looper

  • como atrasar a execução do teste enquanto chamadas de método assíncrono são concluídas

Os aplicativos que usam o Sync ou um aplicativo de backend também exigem (não abordado aqui):

  • um backend de aplicativo separado para testes, com contas de usuário e dados separados

  • um Atlas cluster separado contendo dados somente de teste

Para inicializar o SDK, você precisará fornecer um aplicação ou atividade contexto. Por padrão, isso não está disponível em testes de integração do Android. No entanto, você pode usar o ActivityScenario de teste integrado do Android classe para iniciar uma atividade em seus testes. Você pode usar qualquer atividade do seu aplicação ou criar uma atividade vazia apenas para teste.ActivityScenario.launch() Ligue para com sua classe de atividade como um parâmetro para iniciar a atividade simulada.

Em seguida, use o método ActivityScenario.onActivity() para executar uma lambda na thread principal da atividade simulada. Nesta lambda, você deve chamar a função Realm.init() para inicializar o SDK com sua atividade como um parâmetro. Além disso, você deve salvar o parâmetro passado para sua lambda (a instância recém-criada de sua atividade) para uso futuro.

Como o método onActivity() é executado em um thread diferente, você deve impedir que o teste seja executado até que essa configuração inicial seja concluída.

O exemplo a seguir utiliza um ActivityScenario, uma atividade de teste vazia e um CountDownLatch para demonstrar como configurar um ambiente onde você pode testar seu aplicativo 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
}

Realm A funcionalidade de ,como objetos Live e notificações de alteração, só funciona no Looper threads. Os threads configurados com um objeto Looper transmitem eventos em um loop de mensagens coordenado pelo Looper. Normalmente, as funções de teste não têm um objeto Looper , e a configuração de um objeto para funcionar em seus testes pode ser muito propensa a erros.

Em vez disso, você pode usar o arquivo Activity.runOnUiThread() método da sua atividade de teste para executar lógica em um thread que já tem um Looper configurado. Combine Activity.runOnUiThread() com um CountDownLatch conforme descrito na seção de atraso para impedir que o teste seja concluído e encerrado antes que a lógica seja executada. Na chamada runOnUiThread() , você pode interagir com o SDK da mesma forma que faria normalmente no código do seu aplicativo:

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() {
@Override
public void onSuccess(@NonNull Realm realm) {
Log.v("EXAMPLE", "Successfully opened a realm.");
// read and write to realm here via transactions
testLatch.countDown();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(@NonNull Realm realm) {
realm.createObjectFromJson(Frog.class,
"{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }");
}
});
realm.close();
}
@Override
public void onError(@NonNull 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)
}
}
}

Como o SDK usa chamadas assíncronas para operações comuns, como queries de banco de dados, autenticação e chamadas de função, os testes precisam de uma maneira de aguardar a conclusão dessas chamadas assíncronas. Caso contrário, seus testes serão encerrados antes que suas chamadas assíncronas (ou multisegmentadas) sejam executadas. Este exemplo usa o CountDownLatchincorporado do Java . Siga estas etapas para utilizar um CountDownLatch em seus próprios testes:

  1. Instancie um CountDownLatch com uma contagem de 1.

  2. Depois de executar a lógica assíncrona que seu teste precisa esperar, chame o método countDown() dessa instância CountDownLatch.

  3. Quando você precisar esperar pela lógica assíncrona, adicione um bloco try/catch que lida com um InterruptedException. Nesse bloco, chame o método await() dessa instância CountDownLatch .

  4. Passe um intervalo de tempo limite e unidade para await() e encerre a chamada em uma asserção Assert.assertTrue(). Se a lógica demorar muito, a chamada await() expira, retornando falso e falhando no teste.

O exemplo a seguir demonstra o uso de um CountDownLatch para aguardar a autenticação e abrir um realm de forma assíncrona em um thread 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() {
@Override
public void onSuccess(@NonNull Realm realm) {
Log.v("EXAMPLE", "Successfully opened a realm.");
// read and write to realm here via transactions
testLatch.countDown();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(@NonNull Realm realm) {
realm.createObjectFromJson(Frog.class,
"{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }");
}
});
realm.close();
}
@Override
public void onError(@NonNull 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())
}

Os aplicativos que usam um backend de aplicativo não devem se conectar ao backend de produção para fins de teste pelos seguintes motivos:

  • você deve sempre manter usuários de teste e usuários de produção separados por motivos de segurança e privacidade

  • os testes geralmente exigem um estado inicial limpo, portanto, há uma boa chance de que seus testes incluam um método de configuração ou desmontagem que exclua todos os usuários ou grandes blocos de dados

Você pode usar ambientes para gerenciar apps separados para teste e produção.

Os aplicativos que usam queries Sync ou MongoDB podem ler, gravar, atualizar ou excluir dados armazenados em clusters Atlas conectados. Por motivos de segurança, não deve armazenar dados de produção e dados de teste no mesmo cluster. Além disso, os testes podem exigir mudanças no esquema antes que essas alterações sejam tratadas normalmente em seu aplicativo de produção. Como resultado, você deve utilizar um Atlas cluster separado ao testar seu aplicativo.

O exemplo a seguir mostra um exemplo completo da Junit instrumentado androidTest executando o Realm em testes de integração:

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 {
@Test
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() {
@Override
public void onSuccess(@NonNull Realm realm) {
Log.v("EXAMPLE", "Successfully opened a realm.");
// read and write to realm here via transactions
testLatch.countDown();
realm.executeTransaction(new Realm.Transaction() {
@Override
public void execute(@NonNull Realm realm) {
realm.createObjectFromJson(Frog.class,
"{ name: \"Doctor Cucumber\", age: 1, species: \"bullfrog\", owner: \"Wirt\", _id: 0 }");
}
});
realm.close();
}
@Override
public void onError(@NonNull 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 {
@Test
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())
}
}
}

Dica

Veja também:

Veja o aplicativode Exemplos de Documentação do Realm para obter um exemplo de teste de integração do SDK localmente e com um backend ativo.

Para unir aplicativos Realm de teste que usam Realm, você deve mock Realm (e seu backend de aplicativo, se usar um). Use as seguintes bibliotecas para simular a funcionalidade do SDK:

Para disponibilizar estas bibliotecas para teste unitário em seu projeto Android, adicione o seguinte ao bloco dependencies do seu arquivo build.gradle do aplicativo:

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"

Observação

Compatibilidade de versão

O acoplamento do SDK em testes unitários requer Robolectric, Mockito e Powermock porque o SDK usa chamadas de método Android Native C++ para interagir com o Realm. Como as estruturas necessárias para substituir essas chamadas de método podem ser delicadas, você deve usar as versões listadas acima para garantir que sua simulação seja bem-sucedida. Algumas atualizações de versão recentes (especialmente a versão 4.2+ do Robolectric) podem interromper a compilação de testes de unidade usando o SDK.

Para configurar seus testes de unidade para usar Robolectric, PowerMock e Mockito com o SDK, adicione as seguintes anotações a cada classe de teste de unidade que modela o SDK:

@RunWith(RobolectricTestRunner.class)
@Config(sdk = 28)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28])
@PowerMockIgnore(
"org.mockito.*",
"org.robolectric.*",
"android.*",
"jdk.internal.reflect.*",
"androidx.*"
)
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest(
Realm::class,
RealmConfiguration::class,
RealmQuery::class,
RealmResults::class,
RealmCore::class,
RealmLog::class
)

Em seguida, a bootstrap Powermock globalmente na classe de teste:

// bootstrap powermock
@Rule
public PowerMockRule rule = new PowerMockRule();
// bootstrap powermock
@Rule
var rule = PowerMockRule()

Em seguida, simule os componentes do SDK que podem executar queries do código C++ nativo para que não atinjamos as limitações do ambiente de teste:

// 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))

Depois de concluir a configuração necessária para a simulação, você pode começar a simular componentes e conectar o comportamento dos seus testes. Você também pode configurar o PowerMockito para retornar objetos específicos quando novos objetos de um tipo forem instanciados, de modo que mesmo o código que faz referência ao realm padrão em seu aplicativo não interromperá seus testes:

// 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)

Depois de simular um domínio, você precisará configurar os dados para seus casos de teste. Consulte o exemplo completo abaixo para ver alguns exemplos de como fornecer dados de teste em testes unitários.

O exemplo a seguir mostra um exemplo JUnit test completo simulando o Realm em testes de unidade. Este exemplo testa uma atividade que realiza algumas operações básicas do Realm. Os testes usam o acoplamento para simular essas operações quando essa atividade é iniciada durante um teste de unidade:

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;
@Override
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>() {
@Override
protected String doInBackground(Void... voids) {
String info = "";
info += complexQuery();
return info;
}
@Override
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));
}
@Override
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;
@RunWith(RobolectricTestRunner.class)
@Config(sdk = 28)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*", "jdk.internal.reflect.*", "androidx.*"})
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest({Realm.class, RealmConfiguration.class, RealmQuery.class, RealmResults.class, RealmCore.class, RealmLog.class})
public class TestTest {
// bootstrap powermock
@Rule
public PowerMockRule rule = new PowerMockRule();
// mocked realm SDK components for tests
private Realm mockRealm;
private RealmResults<Cat> cats;
@Before
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;
}
@Test
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();
}
@SuppressWarnings("unchecked")
private <T extends RealmObject> RealmQuery<T> mockRealmQuery() {
return mock(RealmQuery.class);
}
@SuppressWarnings("unchecked")
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
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28])
@PowerMockIgnore(
"org.mockito.*",
"org.robolectric.*",
"android.*",
"jdk.internal.reflect.*",
"androidx.*"
)
@SuppressStaticInitializationFor("io.realm.internal.Util")
@PrepareForTest(
Realm::class,
RealmConfiguration::class,
RealmQuery::class,
RealmResults::class,
RealmCore::class,
RealmLog::class
)
class TestTest {
// bootstrap powermock
@Rule
var rule = PowerMockRule()
// mocked realm SDK components for tests
private var mockRealm: Realm? = null
private var cats: RealmResults<Cat>? = null
@Before
@Throws(Exception::class)
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
}
@Test
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>? {
@Suppress("UNCHECKED_CAST")
return PowerMockito.mock(RealmQuery::class.java) as RealmQuery<T>
}
private fun <T : RealmObject?> mockRealmResults(): RealmResults<T>? {
@Suppress("UNCHECKED_CAST")
return PowerMockito.mock(RealmResults::class.java) as RealmResults<T>
}
}

Dica

Veja também:

Consulte o exemplo de teste de unidade do aplicativo para obter um exemplo de teste de unidade de um aplicativo que usa o Realm.

Voltar

Depuração