@Test public void shouldThrowExceptionIfNoTypeMappingWasFoundWithTransactionWithoutAffectingDbBlocking() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.lowLevel()).thenReturn(internal); when(storIOSQLite.put()).thenReturn(new PreparedPut.Builder(storIOSQLite)); final List<TestItem> items = asList(TestItem.newInstance(), TestItem.newInstance()); final PreparedPut<PutResults<TestItem>> preparedPut = storIOSQLite.put().objects(items).useTransaction(true).prepare(); try { preparedPut.executeAsBlocking(); failBecauseExceptionWasNotThrown(StorIOException.class); } catch (StorIOException expected) { // it's okay, no type mapping was found assertThat(expected).hasCauseInstanceOf(IllegalStateException.class); } verify(storIOSQLite).put(); verify(storIOSQLite).lowLevel(); verify(internal).typeMapping(TestItem.class); verify(internal, never()).insert(any(InsertQuery.class), any(ContentValues.class)); verify(internal, never()).update(any(UpdateQuery.class), any(ContentValues.class)); verifyNoMoreInteractions(storIOSQLite, internal); }
@Test public void shouldFinishTransactionIfExceptionHasOccurredBlocking() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); //noinspection unchecked final DeleteResolver<Object> deleteResolver = mock(DeleteResolver.class); when(deleteResolver.performDelete(same(storIOSQLite), anyObject())) .thenThrow(new IllegalStateException("test exception")); try { new PreparedDeleteCollectionOfObjects.Builder<Object>( storIOSQLite, singletonList(new Object())) .useTransaction(true) .withDeleteResolver(deleteResolver) .prepare() .executeAsBlocking(); failBecauseExceptionWasNotThrown(StorIOException.class); } catch (StorIOException expected) { IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); verify(internal).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal).endTransaction(); verify(storIOSQLite).internal(); verify(deleteResolver).performDelete(same(storIOSQLite), anyObject()); verifyNoMoreInteractions(storIOSQLite, internal, deleteResolver); } }
@NonNull @Override public Person mapFromCursor(@NonNull Cursor cursor) { final StorIOSQLite storIOSQLite = storIOSQLiteFromPerformGet.get(); // BTW, you don't need a transaction here // StorIO will wrap mapFromCursor() into the transaction if needed final long personId = cursor.getLong(cursor.getColumnIndexOrThrow(PersonsTable.COLUMN_ID)); final String personUuid = cursor.getString(cursor.getColumnIndexOrThrow(PersonsTable.COLUMN_UUID)); final String personName = cursor.getString(cursor.getColumnIndexOrThrow(PersonsTable.COLUMN_NAME)); final List<Car> personCars = storIOSQLite .get() .listOfObjects(Car.class) .withQuery( Query.builder() .table(CarsTable.TABLE_NAME) // .where(CarsTable.COLUMN_PERSON_ID + "=?") // .whereArgs(personId) .where(CarsTable.COLUMN_PERSON_UUID + "=?") .whereArgs(personUuid) .build()) .prepare() .executeAsBlocking(); return new Person.Builder(personName).uuid(personUuid).cars(personCars).build(); }
@Test public void shouldThrowExceptionIfNoTypeMappingWasFoundWithTransactionWithoutAffectingDbAsObservable() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); when(storIOSQLite.delete()).thenReturn(new PreparedDelete.Builder(storIOSQLite)); final List<TestItem> items = asList(TestItem.newInstance(), TestItem.newInstance()); final TestSubscriber<DeleteResults<TestItem>> testSubscriber = new TestSubscriber<DeleteResults<TestItem>>(); storIOSQLite .delete() .objects(items) .useTransaction(true) .prepare() .createObservable() .subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertNoValues(); assertThat(testSubscriber.getOnErrorEvents().get(0)) .isInstanceOf(StorIOException.class) .hasCauseInstanceOf(IllegalStateException.class); verify(storIOSQLite).delete(); verify(storIOSQLite).internal(); verify(internal).typeMapping(TestItem.class); verify(internal, never()).delete(any(DeleteQuery.class)); verifyNoMoreInteractions(storIOSQLite, internal); }
private GetObjectStub(boolean withTypeMapping) { this.withTypeMapping = withTypeMapping; storIOSQLite = mock(StorIOSQLite.class); internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); query = Query.builder().table("test_table").build(); rawQuery = RawQuery.builder().query("select * from who_cares").observesTables("test_table").build(); //noinspection unchecked getResolver = mock(GetResolver.class); cursor = mock(Cursor.class); item = new TestItem(); when(cursor.getCount()).thenReturn(1); when(cursor.moveToNext()) .thenAnswer( new Answer<Boolean>() { int invocationsCount = 0; @Override public Boolean answer(InvocationOnMock invocation) throws Throwable { return invocationsCount++ < 1; } }); when(storIOSQLite.get()).thenReturn(new PreparedGet.Builder(storIOSQLite)); when(storIOSQLite.observeChangesInTables(eq(singleton(query.table())))) .thenReturn(Observable.<Changes>empty()); assertThat(rawQuery.observesTables()).isNotNull(); when(storIOSQLite.observeChangesInTables(rawQuery.observesTables())) .thenReturn(Observable.<Changes>empty()); when(getResolver.performGet(storIOSQLite, query)).thenReturn(cursor); when(getResolver.performGet(storIOSQLite, rawQuery)).thenReturn(cursor); when(getResolver.mapFromCursor(cursor)).thenReturn(item); //noinspection unchecked typeMapping = mock(SQLiteTypeMapping.class); if (withTypeMapping) { when(internal.typeMapping(TestItem.class)).thenReturn(typeMapping); when(typeMapping.getResolver()).thenReturn(getResolver); } }
@Test public void verifyBehaviorInCaseOfExceptionWithoutTransactionCompletable() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.lowLevel()).thenReturn(internal); //noinspection unchecked final PutResolver<Object> putResolver = mock(PutResolver.class); when(putResolver.performPut(same(storIOSQLite), anyObject())) .thenThrow(new IllegalStateException("test exception")); final List<Object> objects = singletonList(new Object()); final TestSubscriber<PutResults<Object>> testSubscriber = new TestSubscriber<PutResults<Object>>(); new PreparedPutCollectionOfObjects.Builder<Object>(storIOSQLite, objects) .useTransaction(false) .withPutResolver(putResolver) .prepare() .asRxCompletable() .subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertNoValues(); testSubscriber.assertError(StorIOException.class); //noinspection ThrowableResultOfMethodCallIgnored StorIOException expected = (StorIOException) testSubscriber.getOnErrorEvents().get(0); IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); // Main checks of this test verify(internal, never()).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal, never()).endTransaction(); verify(storIOSQLite).lowLevel(); verify(storIOSQLite).defaultScheduler(); verify(putResolver).performPut(same(storIOSQLite), anyObject()); verifyNoMoreInteractions(storIOSQLite, internal, putResolver); }
void reloadData() { uiStateController.setUiStateLoading(); final Subscription subscription = storIOSQLite .get() .listOfObjects(Tweet.class) .withQuery(TweetsTable.QUERY_ALL) .prepare() .createObservable() // it will be subscribed to changes in tweets table! .delay( 1, SECONDS) // for better User Experience :) Actually, StorIO is so fast that we need // to delay emissions (it's a joke, or not) .observeOn(mainThread()) .subscribe( new Action1<List<Tweet>>() { @Override public void call(List<Tweet> tweets) { // Remember: subscriber will automatically receive updates // Of tables from Query (tweets table in our case) // This makes your code really Reactive and nice! // We guarantee, that list of objects will never be null (also we use // @NonNull/@Nullable) // So you just need to check if it's empty or not if (tweets.isEmpty()) { uiStateController.setUiStateEmpty(); tweetsAdapter.setTweets(null); } else { uiStateController.setUiStateContent(); tweetsAdapter.setTweets(tweets); } } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { // In cases when you are not sure that query will be successful // You can prevent crash of the application via error handler Timber.e(throwable, "reloadData()"); uiStateController.setUiStateError(); tweetsAdapter.setTweets(null); } }); // Preventing memory leak (other Observables: Put, Delete emit result once so memory leak won't // live long) // Because rx.Observable from Get Operation is endless (it watches for changes of tables from // query) // You can easily create memory leak (in this case you'll leak the Fragment and all it's fields) // So please, PLEASE manage your subscriptions // We suggest same mechanism via storing all subscriptions that you want to unsubscribe // In something like CompositeSubscription and unsubscribe them in appropriate moment of // component lifecycle unsubscribeOnStop(subscription); }
@Test public void shouldFinishTransactionIfExceptionHasOccurredSingle() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); //noinspection unchecked final PutResolver<ContentValues> putResolver = mock(PutResolver.class); final List<ContentValues> contentValues = singletonList(mock(ContentValues.class)); when(putResolver.performPut(same(storIOSQLite), any(ContentValues.class))) .thenThrow(new IllegalStateException("test exception")); final TestSubscriber<PutResults<ContentValues>> testSubscriber = new TestSubscriber<PutResults<ContentValues>>(); new PreparedPutContentValuesIterable.Builder(storIOSQLite, contentValues) .withPutResolver(putResolver) .useTransaction(true) .prepare() .asRxSingle() .subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertNoValues(); testSubscriber.assertError(StorIOException.class); //noinspection ThrowableResultOfMethodCallIgnored StorIOException expected = (StorIOException) testSubscriber.getOnErrorEvents().get(0); IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); verify(internal).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal).endTransaction(); verify(storIOSQLite).internal(); verify(putResolver).performPut(same(storIOSQLite), any(ContentValues.class)); verifyNoMoreInteractions(storIOSQLite, internal, putResolver); }
@Test public void shouldFinishTransactionIfExceptionHasOccurredObservable() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); //noinspection unchecked final DeleteResolver<Object> deleteResolver = mock(DeleteResolver.class); when(deleteResolver.performDelete(same(storIOSQLite), anyObject())) .thenThrow(new IllegalStateException("test exception")); final TestSubscriber<DeleteResults<Object>> testSubscriber = new TestSubscriber<DeleteResults<Object>>(); new PreparedDeleteCollectionOfObjects.Builder<Object>( storIOSQLite, singletonList(new Object())) .useTransaction(true) .withDeleteResolver(deleteResolver) .prepare() .createObservable() .subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertNoValues(); testSubscriber.assertError(StorIOException.class); //noinspection ThrowableResultOfMethodCallIgnored StorIOException expected = (StorIOException) testSubscriber.getOnErrorEvents().get(0); IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); verify(internal).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal).endTransaction(); verify(storIOSQLite).internal(); verify(deleteResolver).performDelete(same(storIOSQLite), anyObject()); verifyNoMoreInteractions(storIOSQLite, internal, deleteResolver); }
@Test public void verifyBehaviorInCaseOfExceptionWithoutTransactionBlocking() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.lowLevel()).thenReturn(internal); //noinspection unchecked final PutResolver<Object> putResolver = mock(PutResolver.class); when(putResolver.performPut(same(storIOSQLite), anyObject())) .thenThrow(new IllegalStateException("test exception")); final List<Object> objects = singletonList(new Object()); try { new PreparedPutCollectionOfObjects.Builder<Object>(storIOSQLite, objects) .useTransaction(false) .withPutResolver(putResolver) .prepare() .executeAsBlocking(); failBecauseExceptionWasNotThrown(StorIOException.class); } catch (StorIOException expected) { IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); // Main checks of this test verify(internal, never()).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal, never()).endTransaction(); verify(storIOSQLite).lowLevel(); verify(putResolver).performPut(same(storIOSQLite), anyObject()); verifyNoMoreInteractions(storIOSQLite, internal, putResolver); } }
@Test public void shouldThrowExceptionIfNoTypeMappingWasFoundWithoutTransactionWithoutAffectingDbAsCompletable() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.lowLevel()).thenReturn(internal); when(storIOSQLite.put()).thenReturn(new PreparedPut.Builder(storIOSQLite)); final List<TestItem> items = asList(TestItem.newInstance(), TestItem.newInstance()); final TestSubscriber<PutResults<TestItem>> testSubscriber = new TestSubscriber<PutResults<TestItem>>(); storIOSQLite .put() .objects(items) .useTransaction(false) .prepare() .asRxCompletable() .subscribe(testSubscriber); testSubscriber.awaitTerminalEvent(); testSubscriber.assertNoValues(); assertThat(testSubscriber.getOnErrorEvents().get(0)) .isInstanceOf(StorIOException.class) .hasCauseInstanceOf(IllegalStateException.class); verify(storIOSQLite).put(); verify(storIOSQLite).lowLevel(); verify(storIOSQLite).defaultScheduler(); verify(internal).typeMapping(TestItem.class); verify(internal, never()).insert(any(InsertQuery.class), any(ContentValues.class)); verify(internal, never()).update(any(UpdateQuery.class), any(ContentValues.class)); verifyNoMoreInteractions(storIOSQLite, internal); }
@Test public void shouldFinishTransactionIfExceptionHasOccurredBlocking() { final StorIOSQLite storIOSQLite = mock(StorIOSQLite.class); final StorIOSQLite.Internal internal = mock(StorIOSQLite.Internal.class); when(storIOSQLite.internal()).thenReturn(internal); //noinspection unchecked final PutResolver<ContentValues> putResolver = mock(PutResolver.class); final List<ContentValues> contentValues = singletonList(mock(ContentValues.class)); when(putResolver.performPut(same(storIOSQLite), any(ContentValues.class))) .thenThrow(new IllegalStateException("test exception")); try { new PreparedPutContentValuesIterable.Builder(storIOSQLite, contentValues) .withPutResolver(putResolver) .useTransaction(true) .prepare() .executeAsBlocking(); failBecauseExceptionWasNotThrown(StorIOException.class); } catch (StorIOException expected) { IllegalStateException cause = (IllegalStateException) expected.getCause(); assertThat(cause).hasMessage("test exception"); verify(internal).beginTransaction(); verify(internal, never()).setTransactionSuccessful(); verify(internal).endTransaction(); verify(storIOSQLite).internal(); verify(putResolver).performPut(same(storIOSQLite), any(ContentValues.class)); verifyNoMoreInteractions(storIOSQLite, internal, putResolver); } }
@NonNull @Override public PutResult performPut( @NonNull StorIOSQLite storIOSQLite, @NonNull TweetWithUser tweetWithUser) { // We can even reuse StorIO methods final PutResults<Object> putResults = storIOSQLite .put() .objects(asList(tweetWithUser.tweet(), tweetWithUser.user())) .prepare() // BTW: it will use transaction! .executeAsBlocking(); final Set<String> affectedTables = new HashSet<String>(2); affectedTables.add(TweetsTable.TABLE); affectedTables.add(UsersTable.TABLE); // Actually, it's not very clear what PutResult should we return here… // Because there is no table for this pair of tweet and user // So, let's just return Update Result return PutResult.newUpdateResult(putResults.numberOfUpdates(), affectedTables); }
@OnClick(R.id.tweets_empty_ui_add_tweets_button) void addTweets() { final List<Tweet> tweets = new ArrayList<Tweet>(); tweets.add( Tweet.newTweet( "artem_zin", "Checkout StorIO — modern API for SQLiteDatabase & ContentResolver")); tweets.add( Tweet.newTweet( "HackerNews", "It's revolution! Dolphins can write news on HackerNews with our new app!")); tweets.add(Tweet.newTweet("AndroidDevReddit", "Awesome library — StorIO")); tweets.add( Tweet.newTweet( "Facebook", "Facebook community in Twitter is more popular than Facebook community in Facebook and Instagram!")); tweets.add( Tweet.newTweet( "Google", "Android be together not the same: AOSP, AOSP + Google Apps, Samsung Android")); tweets.add( Tweet.newTweet( "Reddit", "Now we can send funny gifs directly into your brain via Oculus Rift app!")); tweets.add( Tweet.newTweet( "ElonMusk", "Tesla Model S OTA update with Android Auto 5.2, fixes for memory leaks")); tweets.add( Tweet.newTweet( "AndroidWeekly", "Special issue #1: StorIO — forget about SQLiteDatabase, ContentResolver APIs, ORMs suck!")); tweets.add( Tweet.newTweet("Apple", "Yosemite update: fixes for Wifi issues, yosemite-wifi-patch#142")); // Looks/reads nice, isn't it? storIOSQLite .put() .objects(tweets) .prepare() .createObservable() .observeOn( mainThread()) // Remember, all Observables in StorIO already subscribed on // Schedulers.io(), you just need to set observeOn() .subscribe( new Observer<PutResults<Tweet>>() { @Override public void onError(Throwable e) { safeShowShortToast(getActivity(), R.string.tweets_add_error_toast); } @Override public void onNext(PutResults<Tweet> putResults) { // After successful Put Operation our subscriber in reloadData() will receive // update! } @Override public void onCompleted() { // no impl required } }); }
@NonNull @Override public Cursor performGet(@NonNull StorIOSQLite storIOSQLite, @NonNull Query query) { storIOSQLiteFromPerformGet.set(storIOSQLite); return storIOSQLite.internal().query(query); }