I'm implementing an instrumented test to test a Database Data Source class that is using Realm. So now I'm facing some problems about how to use fixtures and how to mock Realm.
My database data source looks like:
public class DatabaseDataSource {
private Realm realm;
public DatabaseDataSource(Realm realm) {
this.realm = realm;
}
public Observable<RealmResults> getContacts(String firstName, String lastName, String city, String zipCode) {
final RealmQuery realmQuery = realm.where(Contact.class);
if(!TextUtils.isEmpty(firstName)) {
realmQuery.contains("firstName", firstName);
}
if(!TextUtils.isEmpty(lastName)) {
realmQuery.contains("lastName", lastName));
}
if(!TextUtils.isEmpty(city)) {
realmQuery.contains("city", city);
}
if(!TextUtils.isEmpty(zipCode)) {
realmQuery.contains("zipCode", zipCode);
}
return realmQuery.findAll()
.asObservable();
}
}
I want to have a list of contacts in my mocked realm so I can check that filtering is working fine. How can I do that?
I've tried doing:
#RunWith(AndroidJUnit4.class)
public class DatabaseDataSourceTest extends BaseInstrumentedTest{
private DatabaseDataSource databaseDataSource;
private List<Contact> contacts;
#Before
public void setup() {
Realm.init(InstrumentationRegistry.getTargetContext());
Realm.setDefaultConfiguration(new RealmConfiguration.Builder().build());
databaseDataSource = new DatabaseDataSource(new DatabaseClient());
}
#Test
public void trial() throws Exception {
subscribeContactsListObservable(databaseDataSource.getContacts("firstName", null, null, null));
assertEquals(2, contacts.size());
}
private void subscribeContactsListObservable(final Observable<RealmResults> observable) {
notaries = null;
observable.map(new Func1<RealmResults, List<Contact>>() {
#Override
public List<Notary> call(RealmResults realmResults) {
return realmResults != null? new ArrayList<>(realmResults) : null;
}
}).observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<List<Contact>>() {
#Override
public void onCompleted() {
contacts = null;
}
#Override
public void onError(Throwable e) {
contacts = null;
}
#Override
public void onNext(List<Contact> contactsList) {
contacts = contactsList;
}
});
}
}
But the test is failing when doing the Observable.subscribe with the following exception:
You can't register a listener from a non-Looper thread or IntentService thread.
What may I do?
Thanks in advance
Well it specifically tells you the solution to your problem in that error message:
You can't register a listener from **a non-Looper thread** or IntentService thread.
This is because asObservable() needs to register a RealmChangeListener in order to listen to changes in the Realm.
The instrumentation thread is a non-looper thread, so that means you can't listen to changes in it.
Solution, you need to either use a Looper thread (like the main thread), or create a Looper thread, and create the Realm instance in that looper thread. Conveniently, RxAndroid features a so-called LooperScheduler which you can create using AndroidSchedulers.from(Looper), which allows you to execute logic on an arbitrary looper thread.
A possibility is looking into how Realm already tests their looper-related stuff with this RunInLooperThread test rule.
Apparently your getContacts() methods run on a non-looper background thread which doesn't work with our change listeners (and thus our asObservable() method).
You can just create the observable instead, but keep in mind that it will emit your list once and then complete. For continuous updates you need to be on a Looper thread.
return Observable.just(realmQuery.findAll());
Here is fragment of one of my tests:
public class PlaybackDatabaseTest extends ApplicationTestCase<Application> {
public PlaybackDao dao;
public PlaybackDatabaseTest(){ super(Application.class);}
#Override
protected void setUp() throws Exception {
super.setUp();
DatabaseModule module = new DatabaseModule();
RealmConfiguration config = module.providePlaybackRealmConfiguration(getContext());
dao = module.providePlaybackDatabase(config);
}
public void testAddingPlaylistAndDeletingDatabase() {
dao.purgeDatabase();
int id1 = 0;
Playlist playlist = createTestPlaylist(id1, 0, 100);
dao.addPlaylist(playlist);
boolean exist1 = dao.isPlaylistExist(String.valueOf(id1));
assertTrue(exist1);
dao.purgeDatabase();
exist1 = dao.isPlaylistExist(String.valueOf(id1));
assertFalse(exist1);
}
}
But, it use Dagger2 to create database 'data access object' .
Realm can work from main thread, use Observable.toblocking() so your
test thread will wait until jub is done.
Realm use Android's Handler for concurrency (.map(), .flatmap() operators return results on Schedulers.computation() by default), so to solve Handler issue use ApplicationTestCase.
Related
I am using Android Room, I have tried to build a utility method where I retrieve values from the Database. The issue I am having is trying to pass the values back from inside the Runnable which is being executed asynchronously.
public static List<Genre> getAll(final Context context){
AsyncTask.execute(new Runnable() {
#Override
public void run() {
AppDatabase db = AppDatabase.getAppDatabase(context.getApplicationContext());
db.genreDao().getAll();
}
});
return null;
}
What methods do I have to pass this value back but still run in 'not' on the Main Thread?
As already mentioned, you can't return a value asynchronously. And I would advise against using static utility methods for working with the db. Also, AsyncTasks are cumbersome to use. Another option is Rx. For example:
#Dao
public interface GenreDao {
#Query("SELECT * FROM Genre")
List<Genre> getAll();
}
public class GenreRepository {
//...
#WorkerThread
public List<Genre> getAll() {
//Any other source management logic could be placed here, i.e. retrieving from cache or network
return genreDao.getAll();
}
}
public class GenreInteractor {
//...
public Single<List<Genre>> getAll() {
return Single
.fromCallable(genreRepository::getAll)
.subscribeOn(Schedulers.io());
}
}
Then you retrieve your genres list like this:
genreInteractor.getAll().subscribe(genres -> {});
Or if you want your callback to be on main thread:
genreInteractor.getAll().observeOn(AndroidSchedulers.mainThread()).subscribe(genres -> {});
I am new with tests.
I have something like next code and wish to cover it with unitTests using the Mockito:
public void doSomeJob(){
//some code before
getMvpView().execute(getObservable());
//some code after
}
private Observable<Boolean> getObservable(){
return Observable.create(new ObservableOnSubscribe<Boolean>() {
#Override
public void subscribe(#NonNull ObservableEmitter<Boolean> e) throws Exception {
Thread.sleep(5000);
e.onNext(true);
e.onComplete();
}
});
}
so questions:
how correct write test for getMvpView().execute(getObservable()); using Mokito?
how can i verify result of getObservable()?
If your private method is not a part of the interface, i.e. cannot be reached from outside the class, it's not something you should test (presumably it's not, since it's private). Mockito in turn doesn't provide mocking of private methods. Thereby you either need to change your interface (make this data available outside) or leave it without testing.
What you should test is the effect of calling the public methods of your class under test. If you do so you will be able to freely refactor the implementation details later, and your tests will still verify that your class works as expected.
I suppose that your code is part of a presenter implementation and the getMvpView() method returns a view interface:
public class MvpPresenterImpl {
private MvpView view;
public void doSomeJob(){
//some code before
getMvpView().execute(getObservable());
//some code after
}
public void attachView(MvpView view) {
this.view = view;
}
private MvpView getMvpView() {
return view;
}
private Observable<Boolean> getObservable(){
return Observable.create(new ObservableOnSubscribe<Boolean>() {
#Override
public void subscribe(#NonNull ObservableEmitter<Boolean> e) throws Exception {
Thread.sleep(5000);
e.onNext(true);
e.onComplete();
}
});
}
}
You can test the effect of doSomeJob() like so:
public class MvpPresenterImplTest {
private MvpPresenterImpl presenter;
private MvpView mockView;
#Before
public void setUp() throws Exception {
// Create a mock view instance so that we can verify method calls on it
mockView = mock(MvpView.class);
// Create our object under test, and set it up with the mock view
presenter = new MvpPresenterImpl();
presenter.attachView(mockView);
}
#Test
public void doSomeJob_callsExecuteOnViewWithCorrectObserver() throws Exception {
// What we want to test is the effect of invoking a public method.
presenter.doSomeJob();
// Verify that the execute method has been called by your class
// under test, and save the parameter for later.
ArgumentCaptor<Observable<Boolean>> paramCaptor =
ArgumentCaptor.<Observable<Boolean>>forClass((Class)Observable.class);
verify(mockView).execute(paramCaptor.capture());
// Get the actual observable that the execute method was called with.
Observable<Boolean> param = paramCaptor.getValue();
// Get a test observer so that we can check what our Observable emits
// (TestObserver is a built-in feature of RxJava, not Mockito.)
TestObserver<Boolean> test = param.test();
// Assert that the Observable behaves as expected
test.assertComplete();
test.assertResult(true);
}
}
I have an application with a LoginActivity, that when the user login correctly, I register to receive messages. And the LoginActivity jumps to MainActivity.
The arriving messages are supposed to be stored in database (Realm), to recover from a Realm instance in Main.
But when the message arrives It crash realm launching this errror:
Exception in packet listener
java.lang.IllegalStateException: Realm access from incorrect thread. Realm objects can only be accessed on the thread they were created.
at io.realm.BaseRealm.checkIfValid(BaseRealm.java:383)
at io.realm.Realm.executeTransactionAsync(Realm.java:1324)
at io.realm.Realm.executeTransactionAsync(Realm.java:1276)
at es.in2.in2tant.LoginActivity.newMessageReceived(LoginActivity.java:124)
at es.in2.in2tant.Connection.Connection$4$1.processMessage(Connection.java:227)
at org.jivesoftware.smack.chat.Chat.deliver(Chat.java:180)
at org.jivesoftware.smack.chat.ChatManager.deliverMessage(ChatManager.java:351)
at org.jivesoftware.smack.chat.ChatManager.access$300(ChatManager.java:53)
at org.jivesoftware.smack.chat.ChatManager$2.processPacket(ChatManager.java:162)
at org.jivesoftware.smack.AbstractXMPPConnection$4.run(AbstractXMPPConnection.java:1126)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1113)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:588)
at java.lang.Thread.run(Thread.java:818)
I'm a bit lost on how Realm works, and I don't know how to make realm accessible across the application without a crash and keep storing this received messages from LoginActivity. Some help, or approaches to achieving this?
LoginActivity.java:
public class LoginActivity extends AppCompatActivity implements ConnectionConnectResponse {
.....
protected void onCreate(Bundle savedInstanceState) {
//Realm Init config:
Realm.init(this);
RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
Realm.deleteRealm(realmConfiguration); // Clean slate
Realm.setDefaultConfiguration(realmConfiguration); // Make this Realm the default
#Override
public void newMessageReceived(final ChatMessage message) {
Logger.d("NEWMESSAGERECEIVED :" + message);
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Message receivedMessage = realm.createObject(Message.class, message.id);
receivedMessage.setBodyMessage(message.message);
receivedMessage.setFrom(message.from);
receivedMessage.setTo(message.to);
receivedMessage.setDelivered(false);
receivedMessage.setMine(false);
receivedMessage.setDate(Calendar.getInstance().getTime());
}
});
//Logger.d("NEWMESSRE: LAST MESSAGE:" + realm.where(Message.class).equalTo("chatID", message.id));
}
#Override
protected void onStart() {
super.onStart();
realm = Realm.getDefaultInstance();
}
#Override
protected void onStop() {
super.onStop();
realm.close();
}
Image of what is needed:
Realm access from incorrect thread. Realm objects can only be accessed
on the thread they were created.
This error message is quite self-explanatory.
As i see you're initializing realm by calling Realm.getDefaultInstance() on the UI thread.
The error is coming from newMessageReceived(), so i guess that method is called from a background thread.
Either obtain a Realm instance on the background thread and use that instead of the global instance:
#Override
public void run () {
Realm backgroundRealm = Realm.getDefaultInstance();
backgroundRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Message receivedMessage = realm.createObject(Message.class, message.id);
receivedMessage.setBodyMessage(message.message);
receivedMessage.setFrom(message.from);
receivedMessage.setTo(message.to);
receivedMessage.setDelivered(false);
receivedMessage.setMine(false);
receivedMessage.setDate(Calendar.getInstance().getTime());
}
});
}
Or, if you would like to stick to the global Realm instance for some reason, then make sure your code is executed on the UI thread by calling runOnUiThread() (or directly posting a Runnable to the message queue of the main thread through a Handler):
#Override
public void newMessageReceived(final ChatMessage message) {
runOnUiThread(new Runnable() {
#Override
public void run() {
realm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Message receivedMessage = realm.createObject(Message.class,
message.id);
receivedMessage.setBodyMessage(message.message);
receivedMessage.setFrom(message.from);
receivedMessage.setTo(message.to);
receivedMessage.setDelivered(false);
receivedMessage.setMine(false);
receivedMessage.setDate(Calendar.getInstance().getTime());
}
});
}
});
}
Just create Realm backgroundRealm = Realm.getDefaultInstance() each time you want to access database and don't forget to close it using realm.close()
Allocate instance before transaction and release it right after transaction is complete, so you won't have linegring connection and by doing so, you perform savings from thread scheduler.
I use 'Data Access Object' interface for manipulations with data and that interface is implemented using Realm. Don't use 'transaction async', use all calls synchronously, but perform calls on 'data access object' from background thread. Good solution for that - rxJava library.
Just get and release Realm instance all the time - library makes it inexpensive.
I haven't use Realm yet. But what i understood from the error message is that ConnectionConnectResponse may be alive when Loginactivity die. So you should pass Realm instance as a parameter inside
newMessageReceived(Realm realm, final ChatMessage message)
and put all the Realm init code in the class where you fire this method.
I'm trying to save my Objects from Retrofit directly into Realm but always getting the Error:"Realm access from incorrect thread".
This is my code:
public class RestaurantRepositoryRetrofit implements IRestaurantRepository {
private RestaurantApi mApi;
private Realm realm;
private IMapper<RestaurantJson,Restaurant> mRestaurantMapper;
public RestaurantRepositoryRetrofit(IMapper<RestaurantJson, Restaurant> restaurantMapper) {
mApi = ApiProvider.getApi().create(RestaurantApi.class);
mRestaurantMapper = restaurantMapper;
// Get a Realm instance for this thread
realm = Realm.getDefaultInstance();
**}
#Override
public Observable<Restaurant> getRestaurantById(String restaurantId) {**
return mApi.getRestaurantById(restaurantId)
.map(new Func1<RestaurantJson, Restaurant>() {
#Override
public Restaurant call(RestaurantJson restaurantJson) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealm(restaurantJson);
}
});
return mRestaurantMapper.transform(restaurantJson);
}
});
}
}
You should open the Realm instance on the background thread that receives the results of the API.
return mApi.getRestaurantById(restaurantId)
.map(new Func1<RestaurantJson, Restaurant>() {
#Override
public Restaurant call(RestaurantJson restaurantJson) {
try(Realm realm = Realm.getDefaultInstance()) {
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealm(restaurantJson);
}
});
return mRestaurantMapper.transform(restaurantJson);
}
}
});
Although if you intend to return a managed RealmObject, you should map out the ID from the saved proxy and then observe on main thread and query with a UI thread instance of Realm using the ID.
realm = Realm.getDefaultInstance(); will return the instance for the thread on which the object is created. But Observable.map() is called on the thread the observable sends the message from.
Since the observable comes from Retrofit this can be 2 options:
If the CallAdapter was created without specifying a scheduler then it will execute on the thread Observable.subscribe was called on.
If the CallAdapter was created with a specific scheduler it will be executed on that thread.
If the option that applies is not the same thread as the one where the object is created the "Realm access from incorrect thread" error will be thrown.
Realm is thread confined, which means that you need to make sure that you make calls on Realm objects and the realm instance on the same thread that you got the reference on. You probably want to use the scheduling method observeOn() from RxAndroid to make sure that you call realm::executeTransaction() on the same thread that you got the realm instance on.
I have a class, DownloadAndSave that extends from AsyncTask. In its doInBackground method, it retrieves data from an http connection and saves the data using OrmLite, but also cleans the old entries from the database. So, something like this:
doInBackground()
{
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
}
I frequently get a DB exception:
attempt to re-open an already-closed object:SQLiteDatabase
in the clearDb() and saveToDb() functions.
And this is bad since old data from the previous call of DownloadAndSave is mixed with the new data from DownloadAndSave.
In my opinion, I need to make sure that when I start a thread, all of the other treads from the DownloadAndSave class have finished, or in other words I need to run at most one instance of DownloadAndSave at a time. So the question is: how do I make sure that only one instance of DownloadAndSave will run in any point of time?
Option 1. Move above:
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
in a separate class that synchronizes against the class object:
public class WorkerClass {
private WorkerListener workerListener;
public static interface WorkerListener {
public void publishWorkProgress(String data);
}
public WorkerClass(WorkerListener workerListener) {
this.workerListener = workerListener;
}
public void performWork() {
synchronized (WorkerClass.class) {
clearDb();
publish("Cleared DB");
dataList = fetchDataFromHttp();
publish("Got http data");
saveToDb(dataList);
publish("There! saved!");
}
}
private void publish(String message) {
if(workerListener != null) {
workerListener.publishWorkProgress(message);
}
}
}
While from your activity:
public class SampleActivity extends Activity {
public void doTheThing() {
new MyAsyncTask().execute();
}
private static class MyAsyncTask extends AsyncTask<Void, String, Void> implements WorkerListener {
#Override
protected Void doInBackground(Void... params) {
new WorkerClass(this).performWork();
return null;
}
#Override
public void publishWorkProgress(String data) {
publishProgress(data);
}
}
}
Option 2: Move above code to an IntentService:
public class WorkerIntentService extends IntentService {
public WorkerIntentService() {
super(null);
}
#Override
protected void onHandleIntent(Intent intent) {
clearDb();
dataList = fetchDataFromHttp();
saveToDb(dataList);
}
}
Using an IntentService guarantees that tasks are executed serially.
Since API version 11 (HONEYCOMB) of the Android API, an AsyncTask can be executed on a given Executor. You can use the default SerialExecutor to execute tasks sequentially.
If you use db operation in doInBackground, you should be locked db for one thread.
public void insertToDb(){
SQliteDatabase db;
...
db.beginTransaction();
...//operation
db.yieldIfContendedSafely();
db.setTransactionSuccessful();
db.endTransaction();
}
I believe that the issue you are facing is that you are starting the AsyncTask from an activity. Your activity is extending ORMLiteBaseActivity which opens the helper (and with that the database) onCreate and closes it onDestroy. When you exit the activity and the background task still hasn't finished then when trying to do write to the database you end up with a closed DB.
ORMLite handles synchronizations internally and i have never needed to do synchronized blocks with it. I use it in all my projects that require a database.
Also for the other answers, the error is a closed database and not concurrent write operations, so synchronization doesn't make sense.