Writing SQLiteOpenHelper test cases in JUnit4 and Mockito - android

I am trying to write test cases for my database.
I have a helper class that extends to SQLiteOpenHelper
DBHelper.java
public DBHelper(Context context) {
super(context, DBConstants.DATABASE_NAME, null, DBConstants.DATABASE_VERSION);
}
and a constructor class that has all the inserts deletes etc.
DBController.java
public DBController open() throws SQLException {
dbHelper = DBHelper.getInstance(context);
database = dbHelper.getWritableDatabase();
return this;
}
my test class
DBControllerTest.java
#Mock
Context mContext;
DBController dbController;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
RenamingDelegatingContext context = new RenamingDelegatingContext(mContext, "test_");
dbController = new DBController(context);
dbController.open();
}
Here when i do dbController.open(), the dbHelper.getWritableDatabase() always returns null.
How do i solve this problem. Also am I mocking it the right way. I have searched this a lot but did not find a solution. What is the best way to test database queries.

You can't mock Context like that, you need to use the instrumentation's Context. Since this test requires Android code and therefore instrumentation, make sure you put it in your test in the androidTest directory.
See this answer for an example.

Related

Handling SQLite singleton instances and its Context dependency in Android

I am currently working on an Android project that performs a lot of communication with the SQLite database. I am also trying to implement a MVP framework within the app.
My current implementation of the Singleton instance is similar to the following. (taken from this post: https://github.com/codepath/android_guides/wiki/Local-Databases-with-SQLiteOpenHelper )
public class PostsDatabaseHelper extends SQLiteOpenHelper {
private static PostsDatabaseHelper sInstance;
public static synchronized PostsDatabaseHelper getInstance(Context context) {
if (sInstance == null) {
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
return sInstance;
}
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
With the existing code above, I call the getInstance method in several Presenter classes, passing into each of them the Context object which was passed on from the Activity/Fragment. The Context objects can be passed across multiple classes.
Instead of the code above, I was thinking of instantiating the databaseHelper only once at the start of the Application, and then all references will then point to a variant of the getInstance method without the context dependency.
EDIT: My main aim here is to remove as much as possible, the presence of the Context object in the Presenter classes, so as to make the code 'cleaner'. Because all the calls to getInstance provide/inject the same type of Context (the Application's context and not an Activity-specific context), I don't see a need to put the Context object as an argument.
public class PostsDatabaseHelper extends SQLiteOpenHelper {
private static PostsDatabaseHelper sInstance;
// called by all other classes
public static synchronized PostsDatabaseHelper getInstance() {
if (sInstance == null) {
//throw error
}
return sInstance;
}
// only called once at the start of the Application
public static void instantiateInstance(Context context){
sInstance = new PostsDatabaseHelper(context.getApplicationContext());
}
private PostsDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
What I would like to know is, will there be any downsides to this approach? Thanks!
You are trading lazy initialization for static initialization.
In general, lazy initialization can amortize the cost of initialization over the life of an application. In this case it seems less important, for two reasons:
It is almost certain that you will need this DB. It seems unlikely that by putting the initialization off, you might avoid having to do it at all.
The Android framework guarantees that the DBHelper constructor can be run from the UI thread: it is not what takes the time. The thing that takes the time is the first call to getWriteableDatabase. The lazy creation of the Helper accomplishes almost nothing.
You might consider making the code even less convoluted, by initializing the DB in the Application like this:
public class DBDrivenApp extends Application implements DBProvider {
// ...
private PostsDatabaseHelper db;
// ...
#Override
public void onCreate() {
super.onCreate();
db = new PostsDatabaseHelper(this);
}
#Override
public PostsDatabaseHelper getDB() { return db; }
// ...
}
... and, better yet, use an IoC framework, like Dagger2, to inject the database instance, so that you can mock it in testing.

How to clean db with ProviderTestCase2 or RenamingDelegatingContext

I do not understand what exactly should I do in order to get a clean and a different db from the one that the app uses.
This is my test class:
public class SQLTest extends ProviderTestCase2{
private static String testDbPrefix = "unitTest_";
public SQLTest (){
super(MyContentProvider.class, MyContract.CONTENT_AUTHORITY);
}
#Override
#Before
public void setUp() throws Exception {
//setContext(InstrumentationRegistry.getTargetContext());
RenamingDelegatingContext context = new RenamingDelegatingContext(InstrumentationRegistry.getTargetContext(), testDbPrefix);
setContext(context);
super.setUp();
}
#Test
public void test1(){
//test logic
}
}
I noticed that it always runs on the db that the app uses, even though I'm using a both ProviderTestCase2 and RenamingDelegatingContext, which are supposed to ensure I'm running with a clean db.
Can anyone explain please what am I missing???
Thanks in advance!

RenamingDelegatingContext not creating a test database

I am trying to write test cases for my app database. This is what I do in the setup method:
#Before
public void testCaseSetUp() {
RenamingDelegatingContext context = new RenamingDelegatingContext(getTargetContext(), "test_");
dbController = new DBController(context);
dbController.open();
}
DBController.java
public DBController(Context c) {
context = c;
}
public DBController open() throws SQLException {
dbHelper = DBHelper.getInstance(context);
database = dbHelper.getWritableDatabase();
return this;
}
RenamingDelegatingContext does not create a new test database instead uses the existing db file. This is causing my test cases to fail as I already have data.
Are you sure that you recreate your SQLiteOpenHelper somewhere inside your DBHelper class? From the looks of it the latter is a singleton, so I can assume you're not recreating it every time. You should do so in your #Before method, because the idea behind using a RenamingDelegatingContext here is that it deletes old databases before opening a new one.
Also it's a good practice to close the SQLiteOpenHelper class after your tests. Something like:
#After
public void tearDown() {
dbHelper.close();
}
If this doesn't work, please post your DBHelper class to verify it's ok.

Use local db when testing with Robolectric 3 and ORMLite

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;

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

Categories

Resources