Robolectric: Testing with ormlite - android

I'm trying to test ORMLite DAOs with robolectric, but database behaviour is not the same as when it's used from my android app. My DAOs are working perfectly well on the android application.
Reading about robolectric shadows and debugging code, I encountered ShadowSQLiteOpenHelper (code here).
Does anyone know if this Shadow is enough to test ormlite daos? Or I have to create my own shadow to achieve that? Any clue/tip/suggestion/example here?
Thanks in advance.
Extra info:
Test method:
#Test
public void basicTest() throws SQLException {
assertNotNull(randomStringResource); // Injection of an android resource: OK
assertThat(randomStringResource, equalTo("Event")); // With correct value: OK
assertNotNull(eventDao); // Dao injection: OK
assertThat(eventDao.countOf(), equalTo(0L)); // Table empty: OK
Event e1 = new Event("e1", new Date());
eventDao.create(e1);
assertNotNull(e1.getId()); // ID generated by OrmLite: OK
assertThat(eventDao.countOf(), equalTo(1L)); // Table not empty: OK
assertThat("e1", equalTo(eventDao.queryForId(e1.getId()).getName())); // Query for inserted event: Throws exception
}
Some of the problems encountered running this test:
Errors querying entities with "camelCased" property names: error thrown at last line of test (related problem). Never had a problem like this running the android app.
When I changed one of these properties name (e.g., isEnabled to enabled) in order to avoid the camelCase problem, the previous error persisted... seems like memory database didn't apply the changes that I made on the entity.
Versions used:
Robolectric 1.1
OrmLite 4.41

Sorry for resurrecting your topic but I ran into the same problem.
I'm using OrmLite 4.45 and Robolectric 2.1.
In ShadowSQLiteCursor.java, cacheColumnNames method calls toLowerCase on each column name. So I decided to extend ShadowSQLiteCursor with my own (which doesn't call toLowerCase):
/**
* Simulates an Android Cursor object, by wrapping a JDBC ResultSet.
*/
#Implements(value = SQLiteCursor.class, inheritImplementationMethods = true)
public class ShadowCaseSensitiveSQLiteCursor extends ShadowSQLiteCursor {
private ResultSet resultSet;
public void __constructor__(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {}
/**
* Stores the column names so they are retrievable after the resultSet has closed
*/
private void cacheColumnNames(ResultSet rs) {
try {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
columnNameArray = new String[columnCount];
for(int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
String cName = metaData.getColumnName(columnIndex);
this.columnNames.put(cName, columnIndex - 1);
this.columnNameArray[columnIndex - 1] = cName;
}
} catch(SQLException e) {
throw new RuntimeException("SQL exception in cacheColumnNames", e);
}
}
}
My answer obviously comes too late but may help others!

Related

Not able to update sqlite_sequence table using RoomDatabase.query

We try to update sqlite_sequence with the following code.
WeNoteRoomDatabase weNoteRoomDatabase = WeNoteRoomDatabase.instance();
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'attachment'"));
However, it has no effect at all. I exam the sqlite_sequence table content using SQLite browser. The counter is not reset to 0.
If we try to run the same query manually using SQLite browser on same SQLite file, it works just fine.
Our Room database is pretty straightforward.
#Database(
entities = {Attachment.class},
version = 6
)
public abstract class WeNoteRoomDatabase extends RoomDatabase {
private volatile static WeNoteRoomDatabase INSTANCE;
private static final String NAME = "wenote";
public abstract AttachmentDao attachmentDao();
public static WeNoteRoomDatabase instance() {
if (INSTANCE == null) {
synchronized (WeNoteRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
WeNoteApplication.instance(),
WeNoteRoomDatabase.class,
NAME
)
.build();
}
}
}
return INSTANCE;
}
}
Any idea what we had missed out?
Additional information : clearing sqlite_sequence is not working in android room
Room doesn't use SQLiteDatabase - but it uses SupportSQLiteDatabase, while it's source code states, that it delegates all calls to an implementation of {#link SQLiteDatabase}... I could even dig further - but I'm convinced, that this is a consistency feature and not a bug.
SQLiteDatabase.execSQL() still works fine, but with SupportSQLiteDatabase.execSQL() the same UPDATE or DELETE queries against internal tables have no effect and do not throw errors.
my MaintenanceHelper is available on GitHub. it is important that one initially lets Room create the database - then one can manipulate the internal tables with SQLiteDatabase.execSQL(). while researching I've came across annotation #SkipQueryVerification, which could possibly permit UPDATE or DELETE on table sqlite_sequence; I've only managed to perform a SELECT with Dao... which in general all hints for the internal tables are being treated as read-only, from the perspective of the publicly available API; all manipulation attempts are being silently ignored.
i think query is wrong, you should try below query
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = attachment"));
I'm using room database version 2.2.5
Here I'm unable to execute this query using Room Dao structure, so make one simple class and access method as like this and I got successful outcomes so this one is tested result. I'm using RxJava and RxAndroid for same.
public class SqlHelper {
private static SqlHelper helper = null;
public static SqlHelper getInstance() {
if (helper == null) {
helper = new SqlHelper();
}
return helper;
}
public Completable resetSequence(Context context) {
return Completable.create(emitter -> {
try {
AppDatabase.getDatabase(context)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='<YOUR_TABLE_NAME>'");
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
}
}
Execute:
SqlHelper.getInstance()
.resetQuizSequence(context)
.subscribeOn(Schedulers.io()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, error -> {});
Table sql_sequence is not managed by Room, so you need to edit it using a SupportSQLiteDatabase.
Try this:
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
weNoteRoomDatabase().getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
This works for me - Room 2.2.6
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
<YourDatabase>.getInstance(mContext).getOpenHelper().getWritableDatabase().execSQL(sqlQuery);

GreenDAO does not persist data across application restart

I want to use GreenDAO for persistence, but I cannot get it to persist my data.
The data is saved and loaded correctly as long as the application is not restarted.
Once i swipe the app away and reopen it from scratch, GreenDAO does not see the previous data (both on the emulator and real device).
This is my entity:
#Entity
public class TestSingleEntity {
#Id(autoincrement = true)
Long id;
int someNumber;
public TestSingleEntity(int someNumber) {
this.someNumber = someNumber;
}
#Generated(hash = 787203968)
public TestSingleEntity(Long id, int someNumber) {
this.id = id;
this.someNumber = someNumber;
}
#Generated(hash = 1371368161)
public TestSingleEntity() {
}
// ... some more stuff
}
This is how I insert entities to database:
Random rnd = new Random();
TestSingleEntity singleEntity = new TestSingleEntity();
singleEntity.setSomeNumber(rnd.nextInt());
DaoSession session = ((MyApp)getApplication()).getDaoSession();
TestSingleEntityDao dao = session.getTestSingleEntityDao();
dao.insert(singleEntity);
Log.d("tgd", "Inserted an entity with id " + singleEntity.getId());
And this is how I read them:
Query query = dao.queryBuilder().orderAsc(TestSingleEntityDao.Properties.SomeNumber).build();
StringBuilder builder = new StringBuilder();
List<TestSingleEntity> result = query.list();
Log.d("size", result.size());
for (TestSingleEntity testSingleEntity : result) {
Log.d("entity", testSingleEntity.toString());
}
As I have said, as long as I stay in the app (moving around in different activities is okay), everytime the insert is called, a new entity with a new ID is created. As soon as I relaunch the app, it goes back to square one.
The setup was taken directly from the GitHub page. What am I doing wrong? Thanks
Disclaimer: GreenDAO has gone through major changes since I last used it so this is purely based on reading their code on the github.
Apparently GreenDAO's poorly documented DevOpenHelper drops all tables on upgrade, so the real question is why is onUpgrade being called when clearly there hasn't been a change to the schema version. Try to look for the log line that mentions dropping the tables as described in the template for DevOpenHelper.
Regardless, using OpenHelper instead should fix the issue.

Java - Realm Migration (v 0.85.0) - Field count does not match

I am attempting my first Realm migration in one of my projects using Realm v 0.85.0. I am migrating from v 0.84.0 but the migration should also work for earlier versions. I have followed the example at https://github.com/realm/realm-java/tree/master/examples/migrationExample/src/main that was linked to in the documentation.
In this migration I am attempting to add two new tables. In order to add each new table my migration code looks like the following:
public class Migration implements RealmMigration {
#Override
public long execute(Realm realm, long version) {
if (version == 0)
{
Table newTableOne = realm.getTable(NewTableOne.class);
newTableOne.addColumn(ColumnType.STRING, "columnOne");
// Add any other needed columns here and repeat process for NewTableTwo
// Rest of migration logic goes here...
version++;
}
return version;
}
}
According to Migration on Realm 0.81.1 the getTable() method will automatically create the table if it does not exist. I do not think this is the problem but I've included it just for completeness.
I am also attempting to add a couple of new columns to an existing table and set default values on these new columns. To do this I am using the following code:
Table existingTable = realm.getTable(ExistingTable.class);
existingTable.addColumn(ColumnType.BOOLEAN, "newColumnOne");
existingTable.addColumn(ColumnType.INTEGER, "newColumnTwo");
// Any other new columns needed here
long newColumnOneIndex = getIndexForProperty(existingTable, "newColumnOne");
long newColumnTwoIndex = getIndexForProperty(existingTable, "newColumnTwo");
for (int i = 0; i < existingTable.size(); i++)
{
userTable.setBoolean(newColumnOneIndex, i, false);
userTable.setLong(newColumnTwoIndex, i, 5);
}
The getIndexForProperty method is pulled directly from the example on Github and looks like:
private long getIndexForProperty(Table table, String name) {
for (int i = 0; i < table.getColumnCount(); i++) {
if (table.getColumnName(i).equals(name)) {
return i;
}
}
return -1;
}
When this migration is run I am getting a RealmMigrationNeededException that states "Field count does not match - expected 22 but was 23". I have looked around StackOverflow and done some research via Google and on the Github wiki but have not been able to find any information relating to this exact "Field count does not match" message.
I have ensured that there is an addColumn line for each new field in each model class and there are not more fields in my model than I am adding columns and vice versa.
Any help that you may be able to provide would be greatly appreciated.

How to write Robolectric (2.3) test using database

Due to last release of Robolectic to version 2.3, it's written that (https://github.com/robolectric/robolectric/releases):
Robolectric now uses a real implementation of SQLite instead of a collection of shadows and fakes. Tests can now be written to verify real database behavior.
I haven't found any "How to" documentation.
I'd like to know how should I implement test on e.g. Activity using SQLiteDatabase query. Where should I put .db file so a test uses it.
You will need to put the .db file under src/test/resources/ folder.
For example, sample.db
Then in your unit test setUp() call:
#Before
public void setUp() throws Exception {
String filePath = getClass().getResource("/sample.db").toURI().getPath();
SQLiteDatabase db = SQLiteDatabase.openDatabase(
(new File(filePath)).getAbsolutePath(),
null,
SQLiteDatabase.OPEN_READWRITE);
// perform any db operations you want here
}
Here is an example how to test database, oprations.
// just a wrapper for the content values map
Agenda agenda = new Agenda();
agenda.setName("MyAgenda");
agenda.setDate("current date");
long rowId = agendaManager.insert(agenda); // the guy who makes database operations
Cursor query = context.getContentResolver().query(AgendaProvider.AGENDA_CONTENT_URI, null, null, null, null);
assertThat(query.getCount()).isEqualTo(1);
query.moveToNext();
Agenda dbAgenda = new Agenda(query);
assertThat(dbAgenda.getRowId()).isPositive();
assertThat(dbAgenda.getRowId()).isEqualTo(rowId);
assertThat(dbAgenda.getName()).isEqualTo(agenda.getName());
assertThat(dbAgenda.getDate()).isEqualTo(agenda.getDate());
An more detailed example may be found here https://github.com/nenick/android-gradle-template/blob/master/UnitTestsRobolectric/src/test/java/com/example/managers/AgendaManagerTest.java

ORMLite joins queries and Order by

I'm tring to make join in two tables and get all columns in both, I did this:
QueryBuilder<A, Integer> aQb = aDao.queryBuilder();
QueryBuilder<B, Integer> bQb = bDao.queryBuilder();
aQb.join(bQb).prepare();
This equates to:
SELECT 'A'.* FROM A INNER JOIN B WHERE A.id = B.id;
But I want:
SELECT * FROM A INNER JOIN B WHERE A.id = B.id;
Other problem is when taking order by a field of B, like:
aQb.orderBy(B.COLUMN, true);
I get an error saying "no table column B".
When you are using the QueryBuilder, it is expecting to return B objects. They cannot contain all of the fields from A in B. It will not flesh out foreign sub-fields if that is what you mean. That feature has not crossed the lite barrier for ORMLite.
Ordering on join-table is also not supported. You can certainly add the bQb.orderBy(B.COLUMN, true) but I don't think that will do what you want.
You can certainly use raw-queries for this although it is not optimal.
Actually, I managed to do it without writing my whole query as raw query. This way, I didn't need to replace my query builder codes (which is pretty complicated). To achieve that, I followed the following steps:
(Assuming I have two tables, my_table and my_join_table and their daos, I want to order my query on my_table by the column order_column_1 of the my_join_table)
1- Joined two query builders & used QueryBuilder.selectRaw(String... columns) method to include the original table's + the columns I want to use in foreign sort. Example:
QueryBuilder<MyJoinTable, MyJoinPK> myJoinQueryBuilder = myJoinDao.queryBuilder();
QueryBuilder<MyTable, MyPK> myQueryBuilder = myDao.queryBuilder().join(myJoinQueryBuilder).selectRaw("`my_table`.*", "`my_join_table`.`order_column` as `order_column_1`");
2- Included my order by clauses like this:
myQueryBuilder.orderByRaw("`order_column_1` ASC");
3- After setting all the select columns & order by clauses, it's time to prepare the statement:
String statement = myQueryBuilder.prepare().getStatement();
4- Get the table info from the dao:
TableInfo tableInfo = ((BaseDaoImpl) myDao).getTableInfo();
5- Created my custom column-to-object mapper which just ignores the unknown column names. We avoid the mapping error of our custon columns (order_column_1 in this case) by doing this. Example:
RawRowMapper<MyTable> mapper = new UnknownColumnIgnoringGenericRowMapper<>(tableInfo);
6- Query the table for the results:
GenericRawResults<MyTable> results = activityDao.queryRaw(statement, mapper);
7- Finally, convert the generic raw results to list:
List<MyTable> myObjects = new ArrayList<>();
for (MyTable myObject : results) {
myObjects.add(myObject);
}
Here's the custom row mapper I created by modifying (just swallowed the exception) com.j256.ormlite.stmt.RawRowMapperImpl to avoid the unknown column mapping errors. You can copy&paste this into your project:
import com.j256.ormlite.dao.RawRowMapper;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.table.TableInfo;
import java.sql.SQLException;
public class UnknownColumnIgnoringGenericRowMapper<T, ID> implements RawRowMapper<T> {
private final TableInfo<T, ID> tableInfo;
public UnknownColumnIgnoringGenericRowMapper(TableInfo<T, ID> tableInfo) {
this.tableInfo = tableInfo;
}
public T mapRow(String[] columnNames, String[] resultColumns) throws SQLException {
// create our object
T rowObj = tableInfo.createObject();
for (int i = 0; i < columnNames.length; i++) {
// sanity check, prolly will never happen but let's be careful out there
if (i >= resultColumns.length) {
continue;
}
try {
// run through and convert each field
FieldType fieldType = tableInfo.getFieldTypeByColumnName(columnNames[i]);
Object fieldObj = fieldType.convertStringToJavaField(resultColumns[i], i);
// assign it to the row object
fieldType.assignField(rowObj, fieldObj, false, null);
} catch (IllegalArgumentException e) {
// log this or do whatever you want
}
}
return rowObj;
}
}
It's pretty hacky & seems like overkill for this operation but I definitely needed it and this method worked well.

Categories

Resources