테스트 - Java SDK
이 페이지의 내용
단위 테스트 또는 통합 테스트를 사용하여 애플리케이션을 테스트할 수 있습니다. 단위 테스트는 애플리케이션 코드에 작성된 논리만 평가합니다. 통합 테스트는 애플리케이션 로직, 데이터베이스 쿼리 및 쓰기, 애플리케이션 백엔드(있는 경우)에 대한 호출을 평가합니다. 단위 테스트는 JVM을 사용하여 개발 시스템에서 실행되고 통합 테스트는 물리적 또는 에뮬레이션된 Android 기기에서 실행됩니다. Android의 내장 계측 테스트를 사용하여 Realm의 실제 인스턴스 또는 앱 백엔드와 통신하여 통합 테스트를 실행할 수 있습니다.
Android는 단위 테스트 및 계측 테스트에 Android 프로젝트의 특정 파일 경로와 폴더 이름을 사용합니다.
테스트 유형 | 경로 |
---|---|
단위 테스트 | /app/src/test |
계측 테스트 | /app/src/androidTest |
SDK는 데이터 저장을 위해 Android 네이티브를 통해 C++ 코드를 사용하기 때문에 단위 테스트는 Realm과의 상호 작용을 완전히 모방해야 합니다. 데이터베이스와의 광범위한 상호 작용이 필요한 논리에는 통합 테스트를 선호합니다.
통합 테스트
이 섹션에서는 Realm SDK를 사용하는 애플리케이션을 통합 테스트하는 방법을 설명합니다. 테스트 환경에서 다루는 개념은 다음과 같습니다.
애플리케이션 컨텍스트 획득(acquiring)
Looper
스레드에서 로직 실행비동기 메서드 호출이 완료되는 동안 테스트 실행을 지연하는 방법
동기화 또는 백엔드 앱을 사용하는 애플리케이션에는 다음 항목도 필요합니다(여기서는 다루지 않음):
테스트용 별도의 앱 백엔드, 별도의 사용자 계정 및 데이터 필요
테스트 전용 데이터가 포함된 별도의 Atlas 클러스터
애플리케이션 컨텍스트
SDK를 초기화하려면 애플리케이션 또는 활동 컨텍스트 를 제공해야 합니다. . Android 통합 테스트에서는 기본값 이 기능을 사용할 수 없습니다. 그러나 Android의 내장 테스트 ActivityScenario 를 사용할 수 있습니다. 클래스를 사용하여 테스트에서 활동을 시작할 수 있습니다. 애플리케이션 의 모든 활동을 사용하거나 테스트용으로 빈 활동을 만들 수 있습니다.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 }
루퍼(Looper) 스레드
라이브 객체 및 변경 알림과 같은 Realm 기능은 Looper 에서만 작동합니다. 스레드. Looper
객체로 구성된 스레드는 Looper
에 의해 조정되는 메시지 루프를 통해 이벤트를 전달합니다. 테스트 함수에는 일반적으로 Looper
객체가 없으며 테스트에서 작동하도록 구성하면 오류가 발생하기 쉽습니다.
대신 Activity.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() { 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) } } }
비동기 호출이 완료되는 동안 테스트 실행 지연
SDK는 데이터베이스 쿼리, 인증, 함수 호출과 같은 일반적인 작업에 비동기 호출을 사용하므로 테스트에는 이러한 비동기 호출이 완료될 때까지 기다릴 방법이 필요합니다. 그렇지 않으면 비동기(또는 다중 스레드) 호출이 실행되기 전에 테스트가 종료됩니다. 이 예제에서는 Java의 내장 CountDownLatch 를 사용합니다. . 자체 테스트에서 CountDownLatch
를 사용하려면 다음 단계를 따르세요.
카운트가 1인
CountDownLatch
를 인스턴스화합니다.테스트가 기다려야 하는 비동기 로직을 실행한 후 해당
CountDownLatch
인스턴스의countDown()
메서드를 호출합니다.비동기 로직을 기다려야 하는 경우
InterruptedException
을 처리하는try
/catch
블록을 추가합니다. 이 블록에서 해당CountDownLatch
인스턴스의await()
메서드를 호출합니다.시간 초과 간격과 단위를
await()
에 전달하고 호출을Assert.assertTrue()
어설션으로 감쌉니다. 로직이 너무 오래 걸리면await()
호출 제한 시간이 초과되어 false를 반환하고 테스트에 실패합니다.
다음 예제는 CountDownLatch
를 사용하여 인증을 기다리고 별도의 스레드에서 비동기적으로 영역을 여는 방법을 보여줍니다.
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()) }
백엔드 테스트
앱 백엔드를 사용하는 애플리케이션은 다음과 같은 이유로 테스트 목적으로 프로덕션 백엔드에 연결해서는 안 됩니다.
보안 및 개인정보 보호를 위해 항상 테스트 사용자와 프로덕션 사용자를 분리해야 합니다.
테스트에는 종종 기존 항목이 없는 초기 상태가 필요하므로, 테스트에 모든 사용자 또는 대량의 데이터를 삭제하는 설정 또는 해체 메서드가 포함되어 있을 가능성이 높습니다.
환경 을 사용하여 테스트용 앱과 프로덕션용 앱을 별도로 관리할 수 있습니다.
Atlas 클러스터 테스트
동기화 또는 MongoDB 쿼리를 사용하는 애플리케이션은 연결된 Atlas 클러스터에 저장된 데이터를 읽거나, 쓰거나, 업데이트하거나, 삭제할 수 있습니다. 보안을 위해 프로덕션 데이터와 테스트 데이터를 동일한 클러스터에 저장하면 안 됩니다. 또한 프로덕션 애플리케이션에서 변경 사항을 정상적으로 처리하기 전에 테스트에 스키마 변경이 필요할 수 있습니다. 따라서 애플리케이션을 테스트할 때는 별도의 Atlas 클러스터를 사용해야 합니다.
전체 예시
다음 예시는 통합 테스트에서 Realm을 실행하고 Junit으로 인스트루먼트된 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 { 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()) } } }
단위 테스트
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를 모방하려면 SDK가 Android 네이티브 C++ 메서드 호출을 사용하여 Realm과 상호 작용하기 때문에 Robolectric, Mockito 및 Powermock이 필요합니다. 이러한 메서드 호출을 재정의하는 데 필요한 프레임워크는 복잡할 수 있으므로, 모방 작업이 성공적으로 수행되려면 위에 나열된 버전을 사용해야 합니다. 일부 최신 버전 업데이트(특히 Robolectric 버전 4.2+)는 SDK를 사용한 단위 테스트 컴파일을 중단시킬 수 있습니다.
단위 테스트에서 SDK와 함께 Robolectric, PowerMock 및 Mockito를 사용하도록 구성하려면 SDK를 모방하는 각 단위 테스트 클래스에 다음 주석을 추가합니다:
그런 다음 테스트 클래스에서 전역적으로 Powermock을 부트스트랩합니다.
// bootstrap powermock public PowerMockRule rule = new PowerMockRule();
// bootstrap powermock 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))
모방에 필요한 설정을 완료한 후에는 구성 요소 모방 및 테스트 동작 연결을 시작할 수 있습니다. 또한 특정 유형의 새 객체가 인스턴스화될 때 특정 객체를 반환하도록 PowerMockito를 구성할 수 있습니다. 따라서 애플리케이션의 기본 영역을 참조하는 코드에서도 테스트가 중단되지 않습니다.
// 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을 모방하는 전체 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; 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> } }