Mocking SQLite-Database while testing Activity with Robolectric - android

In the past few days I started playing around with roboguice, robolectric and mockito. I have a small Android-application with a login-screen containing an AutoCompleteTextView for faster entering the username. The usernames for the AutoCompleteTextView are stored in a sqlite-database.
public class MainActivity extends RoboActivity implements View.OnClickListener {
#InjectView(R.id.startScreen_Login_Button) private Button loginButton;
#InjectView(R.id.startScreen_Cancel_Button) private Button cancelButton;
#InjectView(R.id.startScreen_forgotPwd_TextView) private TextView forgotPWTextView;
#InjectView(R.id.startScreen_Username_AutoCompleteTextView) private AutoCompleteTextView loginUsernameAutoCompleteTextView;
#InjectView(R.id.startScreen_Password_EditText) private EditText loginPasswordEditText;
#Inject private SharedPreferences sharedPreferences;
#Inject SQLiteDBAdapter dbAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
loginButton.setOnClickListener(this);
cancelButton.setOnClickListener(this);
forgotPWTextView.setOnClickListener(this);
// Creating List for startScreen_Username_AutoCompleteTextView
List<User> userList = dbAdapter.getUserList();
ListIterator<User> it = userList.listIterator();
List<String> userStringList = new ArrayList<String>();
User user;
while (it.hasNext()) {
user = it.next();
userStringList.add(user.getName());
}
loginUsernameAutoCompleteTextView.setAdapter(new ArrayAdapter<String>(this, R.layout.select_page_row, userStringList));
}
...
}
I want to test MainActivity using robolectric, trying to mock the database with mockito.
This is my test-class:
#RunWith(CustomRobolectricTestRunner.class)
public class MainActivityTest {
#Mock
SQLiteDBAdapter dbAdapter;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void shouldHaveApplicationName() throws Exception {
String appName = new MainActivity().getResources().getString(R.string.app_name);
assertThat(appName, equalTo("OperationReport"));
}
#Test
public void testButtonsVisible()
{
MainActivity mainActivity = new MainActivity();
mainActivity.onCreate(null);
}
}
Calling mainActivity.onCreate(null); is starting the error-cascade, ending in line Cursor cursor = db.rawQuery(SQL_QUERY, null); of my getUserList-method in my SQLiteDBAdapter:
public List<User> getUserList() {
SQLiteDatabase db = getReadableDatabase();
List<User> userList = new ArrayList<User>();
String SQL_QUERY = "SELECT * FROM User;";
Cursor cursor = db.rawQuery(SQL_QUERY, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
User user = new User();
user.setUserUUID(cursor.getString(0));
user.setName(cursor.getString(1));
user.setPassword(cursor.getString(2));
user.setDateOfBirth(cursor.getString(3));
user.setStaffNumber(cursor.getString(4));
user.setActive(cursor.getInt(5));
user.setUserClass(cursor.getInt(6));
userList.add(user);
cursor.moveToNext();
}
cursor.close();
db.close();
return userList;
}
I read, that a Mock is returning empty stubs of void-methods, and returns null on any other method. As I am mocking the SQLiteDBAdapter-class I am expecting that calling getUserList on my mocked SQLiteDBAdapter returns null. It is not quite clear to me, why he is accessing the original method. I guess it is still using the original SQLiteDBAdapter and not the Mock. What do I have to do to fix this, and how is it working? I ran out of ideas, so any help is appreciated.

Mocking a database to test a DAO makes no sense to me at all. What are you testing? The database. Why eliminate it?
Mocking the database makes sense once you have all your DAO tests passing and it's time to test the service that users it to fulfill a unit of work. You've already tested the DAO and the database, and your service unit test need not be an integration test. By all means mock away in that case.
I don't know a lot about what you're mocking, but when I mock it's for interfaces of my making. The mock provides a stand-in implementation for the interface-typed reference that my client/test is using.
If you're trying to mock a concrete class I'd recommend wrapping that adapter inside an interface-based implementation. It'll be a better abstraction and you'll have an easier time mocking your interface.

Related

How to call Application getDatabase in Room

I downloaded the Room BasicSample app from here:
https://github.com/googlesamples/android-architecture-components
This sample is a readonly database. There is no example to insert a single entity. I am modifying it, and struggling to figure out how to call the getDatabase so I can do a simple insert on the db on a button click -
getDatabase().wordDao().insert(...) ?
How do I get access to the singleton BasicApp and call getDatabase method, and where do I call it from?
Any help is appreciated.
single Insert
#Insert(onConflict = IGNORE)
void insert(WordEntity word);
AppDatabase.java (not sure if this insert method goes here)
private static void insert(final AppDatabase database, final WordEntity word) {
database.wordDao().insert(word);
}
BasicApp.java
public class BasicApp extends Application {
private AppExecutors mAppExecutors;
#Override
public void onCreate() {
super.onCreate();
mAppExecutors = new AppExecutors();
}
public AppDatabase getDatabase() {
return AppDatabase.getInstance(this, mAppExecutors);
} // ==> how do I get access to this?
public DataRepository getRepository() {
return DataRepository.getInstance(getDatabase());
}
}
In the case you are accessing BasicApp class from an activity or service you can just call ((BasicApp)getApplication()).getDatabase().
Depends a little on what class you are working in.
if it is an activity method (like onCreate):
BasicApp basicApp = (BasicApp) this.getApplicationContext();
AppDatabase appDatabase = basicApp.getDatabase();
//... do work here
If you only have a view (like in an onClickListener which passes a view as an arg):
BasicApp basicApp = (BasicApp) view.getContext().getApplicationContext();
AppDatabase appDatabase = basicApp.getDatabase();
//... do work here

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 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

call dao in another for sqlite transaction

i have 2 dao and i need to call method between them
I did something like this but it s really bad because it does a infinite loop and it's does not respect quality standard
What is the proper way to do this please ?
Thank you very much
DAO 1
public class InfractionDAO {
private SQLiteDatabase database;
private SqLiteManager dbHelper;
private OffenderDAO offenderDAO;
public InfractionDAO(Context context) {
dbHelper = new SqLiteManager(context);
offenderDAO= new OffenderDAO (context);
}
List<Infractions> getInfractions(int id) {
offenderDao.getOffender(id);
}
}
DAO 2
public class OffenderDAO {
private SQLiteDatabase database;
private SqLiteManager dbHelper;
private InfractionDAO infractionDAO;
public OffenderDAO (Context context) {
dbHelper = new SqLiteManager(context);
infractionDAO = new InfractionDAO(context);
}
Offender getOffender(int id) {
infractionDAO.getInfractions(id);
}
}
What I would do, is the following:
Solution 1.
create a base class(maybe make abstract?) for both, where they share the common things and in each of the separate classes, offernder and infraction put the different stuff.
Solution 2.
I would create a singleton class that in the constructor initializes both.
like in the following linke:
http://pastebin.com/84g9SgFT

how to test android with ormlite project

anyone could help me with the problem: how to test android apps which uses ormlite?
I'd like to know how to create mocks form dao. This code gets me a dao
public Dao<Account, Integer> getAccountDao() throws SQLException {
if (accountDao == null) {
accountDao = getDao(Account.class);
}
return accountDao;
}
My testing activity looks this:
public class OrmActivity extends OrmLiteBaseActivity<DatabaseHelper> {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
initUi();
initUiListeners();
RuntimeExceptionDao<Account, Integer> accountDao = getHelper().getAccountDataDao();
Account account = new Account();
account.setName("name");
account.setPassword("password");
accountDao.create(account);
}
I dont want to create separate database for tests. Instend of it I want to use mocks.
Best regards
Just in case anyone else stumbles across this post. The issue I had was that you need an instance of an Activity so you can pass the context to the OpenHelperManager.getHelper(android.content.Context context, Class openHelperClass) method.
To get around this I create a ActivityInstrumentationTestCase2 test class to provide the context. You could use MockContext but I understand it to be problematic. This way is simple stupid, sort of :)
At that point I don't bother testing any of the Activity functionality, just database functionality.
public class TestActivity extends ActivityInstrumentationTestCase2<MainFragment> {
public TestActivity() {
super(MainFragment.class);
}
protected void setUp() throws Exception {
super.setUp();
//DatabaseHelper.class is the extended OrmLiteSqliteOpenHelper class
OpenHelperManager.getHelper(getActivity(), DatabaseHelper.class);
}
protected void tearDown() throws Exception {
OpenHelperManager.releaseHelper();
super.tearDown();
}
public void testDetailDiscount() {
//Example code using an entity class that encapsulates ormlite functionality
double total = 95 * 5;
DocumentDetail detail = DocumentDetail.create(getActivity());
assertEquals(total, detail.getTotal());
}
}
Hope it helps
I use Robolectric for unit testing Android apps. Works fine with RoboGuice and Ormlite. Robolectric creates its own database, so a unit test doesn't modify the database on the device you are testing with.
If this doesn't answer your question, please clarify your question. Like, how is not using a separate database related to using mock dao's?

Categories

Resources