android - How can I test a realm repository in a JUnit4 instrumentation test? -


on android device via junit4 instrumentation, want test realm classes.

but java.lang.illegalstateexception: realm opened thread without looper. async queries need handler send results of query

private final realm realm; //this constructed on instrumentation thread during testing, in production it's constructed on main thread.  public observable<localcart> getcart() {     return realm.where(realmcart.class)             .equalto(realmcart.done, true)             .findallasync()  //throws thread w/o looper exception, bc instrumentation thread not have looper             .asobservable()             .map(realmresults::first)             .map(localcart::create); } 

how can achieve goal? test:

    cartdb = new cartdbimpl(datatestdelegate.realm());      cartdb.write(expected);      testsubscriber<localcart> test = new testsubscriber<>();     cartdb.getcart()             .subscribe(test); //exception thrown here      assertions.assertthat(test).hasreceivedvalues(expected); 

two solutions:

1.) replace realm configuration in application inmemory() realm test

@before public void setup() {     instrumentation instrumentation = instrumentationregistry.getinstrumentation();     instrumentation.runonmainsync(new runnable() {         @override         public void run() {             applicationcomponent applicationcomponent = injector.instance.getapplicationcomponent();             appconfig appconfig = applicationcomponent.appconfig();             customapplication customapplication = applicationcomponent.application();             appconfig.setdefaultrealmconfig(new realmconfiguration.builder(customapplication).inmemory()                     .deleterealmifmigrationneeded()                     .build());             if(customapplication.getrealm() != null && !customapplication.getrealm().isclosed()) {                 customapplication.getrealm().close();             }             customapplication.initializerealm();         }     }); 

and use

instrumentation instrumentation = instrumentationregistry.getinstrumentation(); instrumentation.runonmainsync(() -> {    // ... }); 

2.) try somehow make these following classes work snatched realm library instrumentation tests while ago. went 1) because kinda gave on after while, i'm pretty sure worked realm, shouldn't impossible.

/*  * copyright 2014 realm inc.  *  * licensed under apache license, version 2.0 (the "license");  * may not use file except in compliance license.  * may obtain copy of license @  *  * http://www.apache.org/licenses/license-2.0  *  * unless required applicable law or agreed in writing, software  * distributed under license distributed on "as is" basis,  * without warranties or conditions of kind, either express or implied.  * see license specific language governing permissions ,  * limitations under license.  */  package io.realm;  import android.os.looper;  import java.io.unsupportedencodingexception; import java.security.messagedigest; import java.security.nosuchalgorithmexception; import java.util.random; import java.util.concurrent.countdownlatch; import java.util.concurrent.executorservice; import java.util.concurrent.timeunit; import java.util.concurrent.atomic.atomicreference;  import static junit.framework.assert.fail;  public class testhelper {     // returns random key used encrypted realms.     public static byte[] getrandomkey() {         byte[] key = new byte[64];         new random().nextbytes(key);         return key;     }      // returns random key given seed. used encrypted realms.     public static byte[] getrandomkey(long seed) {         byte[] key = new byte[64];         new random(seed).nextbytes(key);         return key;     }      // alloc garbage can. pass maxsize = 0 use it.     public static byte[] allocgarbage(int garbagesize) {         if(garbagesize == 0) {             long maxmemory = runtime.getruntime().maxmemory();             long totalmemory = runtime.getruntime().totalmemory();             garbagesize = (int) (maxmemory - totalmemory) / 10 * 9;         }         byte garbage[] = new byte[0];         try {             if(garbagesize > 0) {                 garbage = new byte[garbagesize];                 garbage[0] = 1;                 garbage[garbage.length - 1] = 1;             }         } catch(outofmemoryerror oom) {             return allocgarbage(garbagesize / 10 * 9);         }          return garbage;     }      // creates sha512 hash of string. can used password encrypted realms.     public static byte[] sha512(string str) {         try {             messagedigest md = messagedigest.getinstance("sha-512");             md.update(str.getbytes("utf-8"), 0, str.length());             return md.digest();         } catch(nosuchalgorithmexception e) {             throw new runtimeexception(e);         } catch(unsupportedencodingexception e) {             throw new runtimeexception(e);         }     }      public static void awaitorfail(countdownlatch latch) {         awaitorfail(latch, 30);     }      public static void awaitorfail(countdownlatch latch, int numberofseconds) {         try {             if(!latch.await(numberofseconds, timeunit.seconds)) {                 fail("test took longer " + numberofseconds + " seconds");             }         } catch(interruptedexception e) {             fail(e.getmessage());         }     }      // clean resource, shutdown executor service & throw background exception     public static void exitorthrow(final executorservice executorservice, final countdownlatch signaltestfinished, final countdownlatch signalclosedrealm, final looper[] looper, final throwable[] throwable)             throws throwable {          // wait signal indicating test's use case done         try {             // if fails want try hard possible cleanup. if fail close resources             // properly, `after()` method throw because tries delete realms             // used. exception in `after()` code mask original error.             testhelper.awaitorfail(signaltestfinished);         } {             // close executor             executorservice.shutdownnow();             if(looper[0] != null) {                 // failing quit looper not execute block responsible                 // of closing realm                 looper[0].quit();             }              // wait block execute & close realm             testhelper.awaitorfail(signalclosedrealm);              if(throwable[0] != null) {                 // throw assertion errors happened in background thread                 throw throwable[0];             }         }     }      public static abstract class task {         public abstract void run()                 throws exception;     }      public static void executeonnonlooperthread(final task task)             throws throwable {         final atomicreference<throwable> thrown = new atomicreference<throwable>();         final thread thread = new thread() {             @override             public void run() {                 try {                     task.run();                 } catch(throwable e) {                     thrown.set(e);                     if(e instanceof error) {                         throw (error) e;                     }                 }             }         };         thread.start();         thread.join();          final throwable throwable = thrown.get();         if(throwable != null) {             throw throwable;         }     } }  /*  * copyright 2016 realm inc.  *  * licensed under apache license, version 2.0 (the "license");  * may not use file except in compliance license.  * may obtain copy of license @  *  * http://www.apache.org/licenses/license-2.0  *  * unless required applicable law or agreed in writing, software  * distributed under license distributed on "as is" basis,  * without warranties or conditions of kind, either express or implied.  * see license specific language governing permissions ,  * limitations under license.  */  package io.realm.rule;  import android.content.context; import android.content.res.assetmanager;  import org.junit.rules.temporaryfolder; import org.junit.runner.description; import org.junit.runners.model.statement;  import java.io.file; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstream; import java.util.collections; import java.util.map; import java.util.set; import java.util.concurrent.concurrenthashmap;  import io.realm.realm; import io.realm.realmconfiguration;  import static org.junit.assert.asserttrue;  /**  * rule creates {@link realmconfiguration } in temporary directory , deletes realm created  * configuration once test finishes. sure close realm instances before finishing test. otherwise  * {@link realm#deleterealm(realmconfiguration)} throw exception in {@link #after()} method.  * temp directory deleted regardless if {@link realm#deleterealm(realmconfiguration)} fails or not.  */ public class testrealmconfigurationfactory         extends temporaryfolder {     private map<realmconfiguration, boolean> map = new concurrenthashmap<realmconfiguration, boolean>();     private set<realmconfiguration> configurations = collections.newsetfrommap(map);     protected boolean unittestfailed = false;      @override     public statement apply(final statement base, description description) {         return new statement() {             @override             public void evaluate()                     throws throwable {                 before();                 try {                     base.evaluate();                 } catch(throwable throwable) {                     unittestfailed = true;                     throw throwable;                 } {                     after();                 }             }         };     }      @override     protected void before()             throws throwable {         super.before();     }      @override     protected void after() {         try {             for(realmconfiguration configuration : configurations) {                 realm.deleterealm(configuration);             }         } catch(illegalstateexception e) {             // throw exception caused deleting opened realm if test case doesn't throw.             if(!unittestfailed) {                 throw e;             }         } {             // delete temp folder.             super.after();         }     }      public realmconfiguration createconfiguration() {         realmconfiguration configuration = new realmconfiguration.builder(getroot()).build();          configurations.add(configuration);         return configuration;     }      public realmconfiguration createconfiguration(string subdir, string name) {         final file folder = new file(getroot(), subdir);         asserttrue(folder.mkdirs());         realmconfiguration configuration = new realmconfiguration.builder(folder).name(name).build();          configurations.add(configuration);         return configuration;     }      public realmconfiguration createconfiguration(string name) {         realmconfiguration configuration = new realmconfiguration.builder(getroot()).name(name).build();          configurations.add(configuration);         return configuration;     }      public realmconfiguration createconfiguration(string name, byte[] key) {         realmconfiguration configuration = new realmconfiguration.builder(getroot()).name(name).encryptionkey(key).build();          configurations.add(configuration);         return configuration;     }      public realmconfiguration.builder createconfigurationbuilder() {         return new realmconfiguration.builder(getroot());     }      // copies realm file assets temp dir     public void copyrealmfromassets(context context, string realmpath, string newname)             throws ioexception {         // delete existing file before copy         realmconfiguration configtodelete = new realmconfiguration.builder(getroot()).name(newname).build();         realm.deleterealm(configtodelete);          assetmanager assetmanager = context.getassets();         inputstream = assetmanager.open(realmpath);         file file = new file(getroot(), newname);         fileoutputstream outputstream = new fileoutputstream(file);         byte[] buf = new byte[1024];         int bytesread;         while((bytesread = is.read(buf)) > -1) {             outputstream.write(buf, 0, bytesread);         }         outputstream.close();         is.close();     } }  /*  * copyright 2015 realm inc.  *  * licensed under apache license, version 2.0 (the "license");  * may not use file except in compliance license.  * may obtain copy of license @  *  * http://www.apache.org/licenses/license-2.0  *  * unless required applicable law or agreed in writing, software  * distributed under license distributed on "as is" basis,  * without warranties or conditions of kind, either express or implied.  * see license specific language governing permissions ,  * limitations under license.  */  package io.realm.rule;  import java.lang.annotation.retention; import java.lang.annotation.target;  import static java.lang.annotation.elementtype.method; import static java.lang.annotation.retentionpolicy.runtime;  /**  * annotation should used along {@link runinlooperthread}  * when annotation present, test method executed on worker thread looper.  * uses {@link org.junit.rules.temporaryfolder} create , open realm.  * annotation param {@link io.realm.rule.runinlooperthread.runnablebefore} can supplied run before  * looper thread.  */ @target(method) @retention(runtime) public @interface runtestinlooperthread {     class<? extends runinlooperthread.runnablebefore> value() default runinlooperthread.runnablebefore.class; }  /*  * copyright 2015 realm inc.  *  * licensed under apache license, version 2.0 (the "license");  * may not use file except in compliance license.  * may obtain copy of license @  *  * http://www.apache.org/licenses/license-2.0  *  * unless required applicable law or agreed in writing, software  * distributed under license distributed on "as is" basis,  * without warranties or conditions of kind, either express or implied.  * see license specific language governing permissions ,  * limitations under license.  */  package io.realm.rule;  import android.os.handler; import android.os.looper;  import org.junit.runner.description; import org.junit.runners.model.statement;  import java.io.printwriter; import java.io.stringwriter; import java.util.linkedlist; import java.util.uuid; import java.util.concurrent.countdownlatch; import java.util.concurrent.executorservice; import java.util.concurrent.executors;  import io.realm.realm; import io.realm.realmconfiguration; import io.realm.testhelper;  import static org.junit.assert.fail;  /**  * rule runs test inside worker looper thread. rule responsible  * of creating temp directory containing realm instance delete it, once test finishes.  *  * realms used in method method annotated {@code @runtestinlooperthread } should use  * {@link runinlooperthread#createconfiguration()} , friends create configurations. failing can  * result in test failing because realm not deleted (reason {@link testrealmconfigurationfactory}  * , class not agree in order delete open realms.  */ public class runinlooperthread         extends testrealmconfigurationfactory {     public realm realm;     public realmconfiguration realmconfiguration;     private countdownlatch signaltestcompleted;     private handler backgroundhandler;      // variables created inside test local , eligible gc.     // need variables survive across different looper     // events (callbacks happening in future), add strong reference     // them duration of test.     public linkedlist<object> keepstrongreference;      @override     protected void before()             throws throwable {         super.before();         realmconfiguration = createconfiguration(uuid.randomuuid().tostring());         signaltestcompleted = new countdownlatch(1);         keepstrongreference = new linkedlist<object>();     }      @override     protected void after() {         super.after();         realmconfiguration = null;         realm = null;         keepstrongreference = null;     }      @override     public statement apply(final statement base, description description) {         final runtestinlooperthread annotation = description.getannotation(runtestinlooperthread.class);         if(annotation == null) {             return base;         }         return new statement() {             private throwable testexception;              @override             public void evaluate()                     throws throwable {                 before();                 class<? extends runnablebefore> runnablebefore = annotation.value();                 if(!runnablebefore.isinterface()) {                     runnablebefore.newinstance().run(realmconfiguration);                 }                 try {                     final countdownlatch signalclosedrealm = new countdownlatch(1);                     final throwable[] threadassertionerror = new throwable[1];                     final looper[] backgroundlooper = new looper[1];                     final executorservice executorservice = executors.newsinglethreadexecutor();                     executorservice.submit(new runnable() {                         @override                         public void run() {                             looper.prepare();                             backgroundlooper[0] = looper.mylooper();                             backgroundhandler = new handler(backgroundlooper[0]);                             try {                                 realm = realm.getinstance(realmconfiguration);                                 base.evaluate();                                 looper.loop();                             } catch(throwable e) {                                 threadassertionerror[0] = e;                                 unittestfailed = true;                             } {                                 try {                                     looperteardown();                                 } catch(throwable t) {                                     if(threadassertionerror[0] == null) {                                         threadassertionerror[0] = t;                                     }                                     unittestfailed = true;                                 }                                 if(signaltestcompleted.getcount() > 0) {                                     signaltestcompleted.countdown();                                 }                                 if(realm != null) {                                     realm.close();                                 }                                 signalclosedrealm.countdown();                             }                         }                     });                     testhelper.exitorthrow(executorservice, signaltestcompleted, signalclosedrealm, backgroundlooper, threadassertionerror);                 } catch(throwable error) {                     // these exceptions should come testhelper.awaitorfail()                     testexception = error;                 } {                     // try hard possible close down gracefully, while still keeping exceptions intact.                     try {                         after();                     } catch(throwable e) {                         if(testexception != null) {                             // both testhelper.awaitorfail() , after() threw exception. make sure aware of                             // fact printing both exceptions.                             stringwriter teststacktrace = new stringwriter();                             testexception.printstacktrace(new printwriter(teststacktrace));                              stringwriter afterstacktrace = new stringwriter();                             e.printstacktrace(new printwriter(afterstacktrace));                              stringbuilder errormessage = new stringbuilder().append("after() threw error shadows test case error")                                     .append('\n')                                     .append("== test case exception ==\n")                                     .append(teststacktrace.tostring())                                     .append('\n')                                     .append("== after() exception ==\n")                                     .append(afterstacktrace.tostring());                             fail(errormessage.tostring());                         } else {                             // after() threw exception                             throw e;                         }                     }                      // testhelper.awaitorfail() threw exception                     if(testexception != null) {                         //noinspection throwfromfinallyblock                         throw testexception;                     }                 }             }         };     }      /**      * signal test has completed.      */     public void testcomplete() {         signaltestcompleted.countdown();     }      /**      * signal test has completed.      *      * @param latches additional latches wait before set test completed flag.      */     public void testcomplete(countdownlatch... latches) {         for(countdownlatch latch : latches) {             testhelper.awaitorfail(latch);         }         signaltestcompleted.countdown();     }      /**      * posts runnable worker threads looper.      */     public void postrunnable(runnable runnable) {         backgroundhandler.post(runnable);     }      /**      * tear down logic guaranteed run after looper test has either completed or failed.      * run on same thread looper test.      */     public void looperteardown() {     }      /**      * if implementation of supplied annotation, {@link runnablebefore#run(realmconfiguration)}      * executed before looper thread starts. populating realm before test.      */     public interface runnablebefore {         void run(realmconfiguration realmconfig);     } } 

Comments