Use local db when testing with Robolectric 3 and ORMLite - android

I am working on an Android app that uses OrmLite to connect to the SQLite db.
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
...
private static final String DATABASE_NAME = "db.sqlite";
private static DatabaseHelper helper = null;
private DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, BuildConfig.DATABASE_VERSION);
}
public static synchronized DatabaseHelper getHelper(Context context) {
if (helper == null) {
helper = new DatabaseHelper(context);
}
return helper;
}
}
To fetch data from the db, I have some helper classes, they use some DAO.
public class AccountsDBHelper {
public List<Account> getAllAccounts(Context context) {
DatabaseHelper dbHelper = DatabaseHelper.getHelper(context);
Dao<Acount, Integer> daoAccounts = dbHelper.getAccountsDao();
...
...
...
}
}
I have in place Robolectric 3 to test my code, but I am having hard time to understand how to use together Robolectric with ORMLite.
My idea is to have a mock database.sqlite in assets, following the same structure as the one I have in production.
This database will be prefilled with data from test accounts, and use that for all my tests.
For example, if I want to test the ProductsProvider class, I should do:
#RunWith(MyTestRunner.class)
public class AccountsDBHelperTest {
#Test
public void testGetAllAccounts() {
List<Accounts> accounts= AccountsDBHelper.getAllAccounts(getTestContext());
assertNotNull(accounts);
assertFalse(accounts.isEmpty());
}
}
Notice that AccountsDBHelper.getAllAccounts() will use the DatabaseHelper, which will use the db in the Android app assets, and not my local production database file. How can I modify my code of the tests to have them using a local db added as an asset ? Without touching the real code of the app? Any help will be very welcome, thank you...

The missing link was to point to the path of the the local database
String dbPath = RuntimeEnvironment.application.getPackageResourcePath() + DB_FOLDER + dbName;

Related

Use mock db when testing with Robolectric and ORMLite

I am working on an Android app that uses OrmLiteSqliteOpenHelper to connect to the SQLite db.
public class MyDatabaseHelper extends OrmLiteSqliteOpenHelper {
...
private static final String DATABASE_NAME = "mydb.sqlite";
private static MyDatabaseHelper helper = null;
private MyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, BuildConfig.DATABASE_VERSION);
}
public static synchronized MyDatabaseHelper getHelper(Context context) {
if (helper == null) {
helper = new MyDatabaseHelper(context);
}
return helper;
}
}
To fetch data from the db, I have some provider classes, they use some DAO.
public class ProductsProvider {
public static List<Products> getProducts(Context context) {
MyDatabaseHelper helper = MyDatabaseHelper.getHelper(context);
Dao<Product, String> daoProducts = helperDatabase.getProductDao();
...
...
...
}
}
I have in place Robolectric to test my code, but I am having hard time to understand how to use together Robolectric with ORMLite.
My idea is to have a mock database.sqlite, prefilled with the same structure and data I normally have, and use that for all my tests. For example, if I want to test the ProductsProvider class, I should do:
#RunWith(MyTestRunner.class)
public class ProductsProviderTest extends MyTestCase {
#Test
public void testDb() {
List<Products> products = ProductsProvider.getProducts(getTestContext());
assertNotNull(products);
assertFalse(products.isEmpty());
}
}
Notice that ProductsProvider.getProducts() will use MyDatabaseHelper, which will use the db in the standard location of the Android app, and not my local file.
How can modify my code for the tests to have the tests using a local db added as an asset or a resource, without touching the real code of the app?
For anyone who can't stand the 6-12-second startup times of Robolectric 3 + Gradle, the following works with Android's built-in JUnit4 tests (DbHelper extends OrmLiteSqliteOpenHelper).
#RunWith(AndroidJUnit4.class)
public class DbTest {
private DbHelper dbHelper;
#Before
public void setUp() throws Exception {
Context context = new RenamingDelegatingContext(
InstrumentationRegistry.getTargetContext(), "test_");
dbHelper = new DbHelper(context);
}
}
There are probably many optimisations you could make, but I can run a small, I/O-heavy suite that wipes the DB each time on GenyMotion in 2.5 seconds.
If you haven't been keeping up-to-date with it, I definitely recommend checking out what Google has done with its testing features recently. The intro on the Robolectric site is a flat-out lie at this point.
Here how we are doing it.
We put sample DB file in src/test/res folder
We run next code before DB test:
private void copyTestDatabase( String resourceDBName )
throws URISyntaxException, IOException
{
String filePath = getClass().getResource( resourceDBName ).toURI().getPath();
String destinationPath = new ContextWrapper( Robolectric.application.getApplicationContext() ).getDatabasePath(
DatabaseHelper.DATABASE_NAME ).getAbsolutePath();
Files.copy( new File( filePath ), new File( destinationPath ) );
}
Be careful since these tests are super long

correct use of SQLiteOpenHelper (and some consultation)

I have the following code below.
I am creating a database in my application that uses SQLiteOpenHelper.
I have couple of concerns and would appreciate some consults.
Direct answers for these were not found on stack overflow as they might be subjective.
1 - I will be using this database from several activities. However I am not planning on making this a singleton to avoid leaks, but rather I will be getting the getWritableDatabase() and getReadableDatabase() inside each method. I plan on doing a db.close() inside each activity's onDestroy() .Is this advisable ? given my app has couple of activites and is not a huge app.
2 - I am not following and DAO model, nor I am using a different class for every table.
The way I see it, I don't need to. Do I ?
3 - (A question rather than consult)
In the code below, I am not creating a database of the form
private SQLiteDatabase database;
So all the references to the database (from my activities) are being done via the methods in the same subclassed SQLiteOpenHelper, therefore I am referencing the physically created database directly via getWritableDatabase and getReadableDatabase.
Do I need to create an instance of SQLiteDatabase and use it ? Even inside the subclass of SQLiteOpenHelper ?
Below is the code.
public class DbHelper extends SQLiteOpenHelper
{
private static final String DATABASE_NAME = "myDbName";
private static final String DATABASE_TABLE = "myTable";
private static final int DATABASE_VERSION = 1;
private Context ctx;
public DbHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
this.ctx = context;
}
#Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL("CREATE TABLE myTable(_id INTEGER PRIMARY KEY, title TEXT);");
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
//nothing here now. maybe later.
}
public void insertTitle(String title)
{
ContentValues titleCV = new ContentValues();
titleCV .put("title", title);
getWritableDatabase().insert(DATABASE_TABLE, null, titleCV );
}
public void getTitles()
{
Cursor result = getReadableDatabase().rawQuery("SELECT _id, title FROM myTable", null);
while (result.moveToNext())
{
int id = result.getInt(0);
String titleGotten= result.getString(1);
}
result.close();
}
Q1
If you have a scenario within your app that have two parallel threads accessing the database, use a single instance of the SQLiteOpenHelper (singleton or member in the Application or whatever). If not you don't need to.
about calling db.close(), if it is in the onDestroy(), then it's fine.
Q2
a DAO is an abstraction layer to ease maintaining and scaling your project. If you are not going to scale or maintain your code (upcoming releases or something), then I suppose you don't need one.
Q3
You don't need to create an instance of SQLiteDatabse. when you call getReadableDatabase() or getWritableDatabase(), SQLiteOpenHelper creates and maintains an instance. The same instance is used the next time you call getReadable\WritableDatabase().
let me know if you still have questions.
step 1: make a staic instace of SqliteOpenHelper
step 2: you never close conexion to database, sqlite manage itself the sequencial access to write or read :)
private static ControladorBBDD instancia;
my class: public class ControladorBBDD extends SQLiteOpenHelper {
default :
private ControladorBBDD(Context ctx_p) throws Exception {
super(ctx_p, DB_NAME, null, DATABASE_VERSION);
try {
ControladorBBDD.ctx = ctx_p;
DB_PATH = ctx.getDatabasePath(DB_NAME).getAbsolutePath();
String myPath = DB_PATH;// + DB_NAME;
this.createDataBase();
db = SQLiteDatabase.openDatabase(myPath, null,
SQLiteDatabase.OPEN_READWRITE);
} catch (SQLiteException ex) {
Conexiones.escribirLog(Log.getStackTraceString(ex),
ctx.getString(R.string.versionReal));
db.close();
}
}
and my way to implement a conexion to database:
public static synchronized ControladorBBDD getBBDD(Context ctx_p)
throws Exception {
if (instancia == null) {
instancia = new ControladorBBDD(ctx_p);
}
return instancia;
}
and to call it from activities:
dblectura = ControladorBBDD.getBBDD(getApplicationContext());
where private ControladorBBDD dblectura;
i hope that it helps, important thing is that you use applicationContext, no Activity context ;))
well if i were u i would create a class and the dbhelper as a subclass then i would use a open and a close function for main class and also the insert
whenever i want to use database i do it like this
mainclass mc=new mainclass(this);
mc.open();
mc.insert();
mc.close();

Android databases are too big to be created on onCreate

On my app I make use of two datatabases.
This is the class that handles the database management and all the query that are made to it.
public class Database {
private DbHelper DBHelper;
private final Context Context;
private SQLiteDatabase MyDBone, MyDBtwo;
static Context ctx;
private static class DbHelper extends SQLiteOpenHelper {
public DbHelper(Context context, String dbName, int dbVersion) {
super(context, dbName, null, dbVersion);
}
#Override
public void onCreate(SQLiteDatabase db) {
// This is where the two databases are created
}
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVesion) {
// database upgrades are handled here
}
}
}
// database constructor
public Database(Context c) {
Context = c;
ctx = c;
}
// database open
public Database open() throws SQLException {
DBHelper = new DbHelper(Context, BD_NAME, BD_VERSION);
// I have here some if code to decide witch one of the bellow is used
if{
MyDBone = DBHelper.getWritableDatabase();
} else{
MyDBtwo = DBHelper.getWritableDatabase();
}
return this;
}
// database close
public void close() {
DBHelper.close();
}
public Cursor getData(........) {
// My querys are made here
}
}
My problem is that the databases are too big. In the onCreate method I'm getting the error: The code of method onCreate(SQLiteDatabase) is exceeding the 65535bytes limit. On the other side, my app is getting very big on size.
I would like to know what's the best way to address this issue since I can't change my databases.
Since my app must be run offline I can't make query's on a webserver.
I beleive that the best aproach would be to, on the first run of the app, download the databases from somewhere on the internet (drive, dropbox or other side) but since my programming skils are a little green I must pospone this to a must do in the future.
Is it possible, maintaining my Database class, prepack the apk with the databases and install them on the sdcard? On the other side this will increase the apk size (the total of the databases is 15 mb).
Please advise on the best way to address this issue.
Regards,
favolas

Sharing database connection

I just need some validation that this is a workable way to share a SQLlite database connection across my Activities and Services of my app, by using a helper class:
public class DatabaseHelper
{
private static DBItems dbItems;
private static SQLiteDatabase sdbItemsRead, sbItemsWrite;
public static synchronized DBItems getHelper(Context context)
{
if (dbItems == null)
dbItems = new DBItems(context);
return dbItems;
}
public static synchronized SQLiteDatabase select(Context context)
{
if (sdbItemsRead == null)
sdbItemsRead = getHelper(context).getReadableDatabase();
return sdbItemsRead;
}
public static synchronized SQLiteDatabase write(Context context)
{
if (sbItemsWrite == null)
sbItemsWrite = getHelper(context).getWritableDatabase();
return sbItemsWrite;
}
}
In my code I'm calling this to select data:
DatabaseHelper.select(this).rawQuery("SELECT * FROM TABLE");
and this to write data:
DatabaseHelper.write(this).update("tbl_items", cv, "[id] = ?", new String[] { itemId.toString() });
I need to do this because I'm calling the database from different threads (UI and background) and I was getting "database is locked" exceptions thrown.
If you are creating database and calling in some activities then it will work fine, but if you want that your database should run along with service then it is always better to write the database in custom Content Provider.
Custom Content Provider will help you to run your database in background along with service.
you can refer the custom content provider from this link.

In Android, this the correct way to setup SQLiteDatabase helpers?

Exception:
CREATE TABLE android_metadata failed
Failed to setLocale() when constructing, closing the database
android.database.sqlite.SQLiteException: database is locked
My app works fine and has no db issues, except when onUpgrade() is called.
When onUpgrade is automatically called, it tries to use the CarManager class below to do data manipulation required for the upgrade. This fails because the db is locked.
Because this seems like it should be a normal thing to do, it seems that I must not be structuring the following code correctly (two classes follow, a helper and a table manager):
public class DbHelper extends SQLiteOpenHelper {
private Context context;
//Required constructor
public DbAdapter(Context context)
{
super(context, "my_db_name", null, NEWER_DB_VERSION);
this.context = context;
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
overrideDB = db;
CarManager.migrateDataForOnUpgrade(context);
}
}
public class CarManager {
DbHelper dbHelper;
public CarManager(Context context)
{
dbHelper = new DbHelper(context);
}
public void addCar(String make, String model)
{
ContentValues contentValues = new ContentValues();
contentValues.put("make", make);
contentValues.put("model", model);
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.insert("car", null, contentValues);
db.close();
}
public static void migrateDataForOnUpgrade()
{
//Code here that migrates data when onUpgrade() is called
//Db lock happens here
}
}
Any ideas?
Do people set up table manager (ex: dao) differently than this?
edit: I talked to the google team # android developer hours, and they said onUpgrade3 was never meant to do anything like structural changes (alters). So yes, it seems like there are some hacks that must be used in many instances right now.
I use the following model by extending the Application class. I maintain a single static instance of my db helper which all other app components use...
public class MyApp extends Application {
protected static MyAppHelper appHelper = null;
protected static MyDbHelper dbHelper = null;
#Override
protected void onCreate() {
super.onCreate();
...
appHelper = new MyAppHelper(this);
dbHelper = MyAppHelper.createDbHelper();
dbHelper.getReadableDatabase(); // Trigger creation or upgrading of the database
...
}
}
From then on any class which needs to use the db helper simply does the following...
if (MyApp.dbHelper == null)
MyApp.appHelper.createDbHelper(...);
// Code here to use MyApp.dbHelper

Categories

Resources