Docs Menu
Docs Home
/ /
Atlas Device SDK
/ /

テスト - Java SDK

項目一覧

  • 統合テスト
  • アプリケーションコンテキスト
  • ルーパー スレッド
  • 非同期呼び出しが完了している間にテストの実行を遅延させる
  • バックエンドのテスト
  • Atlas クラスターのテスト
  • 完全な例
  • ユニットテスト
  • 完全な例

ユニット テストまたは 統合テスト を使用してアプリケーションをテストできます。 ユニット テストは、アプリケーションのコードに書き込まれたロジックのみを評価します。 統合テストは、アプリケーション ロジック、データベース クエリと書込み (write)、およびアプリケーションのバックエンドへの呼び出しを評価します(存在する場合)。 ユニット テストは JVM を使用して開発マシン上で実行され、統合テストは物理またはエミュレートされた Android デバイスで実行されます。 Android に組み込まれている測定テストを使用して、Realm の実際のインスタンスまたはアプリ バックエンドと通信することで、統合テストを実行できます。

Android は、ユニット テストとインストリング テスト用に Android プロジェクト内の特定のファイル パスとフォルダー名を使用します。

テスト タイプ
パス
ユニットテスト
/app/src/test
機能化テスト
/app/src/androidTest

SDK はデータ ストレージに Android Native 経由の C++ コードを使用するため、ユニット テストでは Realm との相互作用を完全にモックする必要があります。 データベースとの広範なインタラクションを必要とするロジックは、統合テストを優先します。

このセクションでは、Realm SDK を使用するアプリケーションの統合テストの方法を説明します。 テスト環境では、次の概念がカバーされます。

  • アプリケーションコンテキストの取得

  • Looperスレッドでロジックを実行

  • 非同期メソッド呼び出しが完了している間にテスト実行を遅延させる方法

同期 または バックエンド アプリを使用するアプリケーションには、 も必要です(ここでは説明されていません)。

  • テスト用の別のアプリバックエンド(別のユーザーアカウントとデータ)

  • テスト専用データを含む別の Atlas クラスター

SDK を初期化するには、アプリケーションまたはアクティビティ コンテキスト を提供する必要があります 。これは、Android 統合テストではデフォルトでは使用できません。ただし、Android に組み込まれているテスト、 アクティビティシナリオ は クラスを使用して、テストでアクティビティを開始します。アプリケーションの任意のアクティビティを使用することも、テスト専用の空のアクティビティを作成することもできます。シミュレーターされたアクティビティを開始するには、アクティビティクラスをパラメーターとして指定してActivityScenario.launch() を呼び出します。

次に、 ActivityScenario.onActivity()メソッドを使用して、シミュレートされたアクティビティのメインスレッドで Lambda を実行します。 このLambdaでは、Realm.init() 関数を呼び出して、アクティビティをパラメータとして SDK を初期化する必要があります。 さらに、 Lambda (アクティビティの新しく作成されたインスタンス)に渡される パラメータを後で使用するために保存する必要があります。

onActivity()メソッドは別のスレッドで実行されるため、この初期設定が完了するまでさらにテストの実行をブロックする必要があります。

次の例では、 ActivityScenario 、空のテスト アクティビティ、 CountDownLatchを使用して、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 機能は Double でのみ動作します スレッド。Looperオブジェクトで構成されたスレッドは、 Looperによって調整されたメッセージ ループを介してイベントを渡します。 テスト関数には通常、 Looperオブジェクトは含まれず、テストで機能するようにオブジェクトを構成すると非常にエラーが発生する可能性があります。

代わりに、 アクティビティ.runOnUiThread() を使用できます。テスト アクティビティのメソッドとして、 Looperがすでに構成されているスレッドでロジックを実行します。 遅延のセクションで説明されているように、 Activity.runOnUiThread()CountDownLatchを組み合わせて、ロジックが実行される前にテストが完了して終了するのを防ぎます。 runOnUiThread()呼び出し内では、アプリケーション コードで通常実行されるのと同様に SDK を操作できます。

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

SDK は、データベース クエリ、認証、関数呼び出しなどの一般的な操作に非同期呼び出しを使用するため、テストには非同期呼び出しが完了するまで待機する方法が必要です。 そうしないと、非同期(またはマルチスレッド)呼び出しが実行される前にテストが終了します。 この例では Java に組み込まれている CountDownLatch を使用します 。独自のテストでCountDownLatchを使用するには、次の手順に従います。

  1. カウント 1 でCountDownLatchをインスタンス化します。

  2. テストに待機する必要がある非同期ロジックを実行した後、そのCountDownLatchインスタンスのcountDown()メソッドを呼び出します。

  3. 非同期ロジックを待機する必要がある場合は、 InterruptedExceptionを処理するtry / catchブロックを追加します。 そのブロックで、そのCountDownLatchインスタンスのawait()メソッドを呼び出します。

  4. タイムアウト間隔とユニットをawait()に渡し、呼び出しをAssert.assertTrue()アサーションでラップします。 ロジックに時間がかかりすぎる場合、 await()呼び出しはタイムアウトし、false を返し、テストに失敗します。

次の例えでは、 CountDownLatchを使用して認証を待機し、Realm を別のスレッドで非同期に開く方法を示します。

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())
}

App バックエンドを使用するアプリケーションは、次の理由で、テスト目的で本番環境のバックエンドに接続しないでください。

  • セキュリティとプライバシーの理由から、テストユーザーと本番ユーザーは常に分離する必要があります

  • テストにはクリーンな初期状態が必要なことが多いため、テストにはすべてのユーザーまたは大規模なデータチャンクを削除するセットアップまたは引き継ぎ方法が含まれる可能性が高くなります。

環境を使用して、テストと本番用に別々のアプリを管理できます。

Sync クエリまたはMongoDB クエリを使用するアプリケーションは、接続された Atlas クラスターに保存されているデータの読み取り、書込み、更新、または削除を行うことができます。 セキュリティ上の理由から、本番データとテスト データを同じクラスターに保存しないでください。 さらに、テストでは本番アプリケーションでグレースフルに処理される前にスキーマの変更が必要になる場合があります。 そのため、アプリケーションをテストするときは、別の Atlas クラスターを使用する必要があります。

次の例は、統合テストで Realm を実行する MongoDB 2.4.2 でインストールされた完全なandroidTestの例を示しています。

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())
}
}
}

Tip

以下も参照してください。

Realm Documentation サンプル アプリ を参照してください SDK をローカル、およびライブ バックエンドと統合テストする例について説明します。

Realm を使用する Realm アプリケーションのユニットテストには、 モック が必要です Realm (およびアプリケーション バックエンド(使用している場合))。SDK 機能をモックするには、次のライブラリを使用します。

これらのライブラリを Android プロジェクトのユニット テストで使用できるようにするには、アプリケーションのbuild.gradleファイルのdependenciesブロックに以下を追加します。

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"

注意

バージョンの互換性

ユニット テストで SDK をモック化するには、Robolectric、Mockto、Powermock が必要です。SDK は Android Native C++ メソッド呼び出しを使用して Realm を操作するためです。 これらのメソッド呼び出しをオーバーライドするのに必要なフレームワークは脆弱な可能性があるため、モックが成功することを確認するために上記でリストされたバージョンを使用する必要があります。 最近の一部のバージョン更新(特に Robolectric バージョン 4.2 以降)により、SDK を使用したユニット テストのコンパイルが中断される可能性があります。

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
)

次に、テスト クラスで Powermock をグローバルにブートストラップします。

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

次に、ネイティブ C++ コードをクエリする可能性のある SDK のコンポーネントをモック化し、テスト環境の制限に該当しないようにします。

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

モック作成に必要なセットアップが完了したら、テスト用のコンポーネントとワイヤ動作のモックを開始できます。 また、タイプの新しいオブジェクトがインスタンス化されるときに特定のオブジェクトを返すように PowerMockto を構成できるため、アプリケーション内のデフォルトの Realm を参照するコードでもテストを中断することはありません。

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

Realm をモック化したら、テストケースのデータを構成する必要があります。 ユニット テストでテスト データを提供する方法の例については、以下の完全な例を参照してください。

次の例は、ユニット テストで Realm をモデリングする完全な JUnit testの例を示しています。 この例では、いくつかの基本的な Realm 操作を実行するアクティビティをテストしています。 テストは、ユニット テスト中にそのアクティビティが開始されたときにそれらの操作をシミュレートするためにモックを使用します。

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>
}
}

Tip

以下も参照してください。

ユニットテストのサンプルアプリ を参照してください Realm を使用するアプリケーションのユニット テストの例については、 を参照してください。

戻る

デバッグ