1package com.xtremelabs.robolectric.shadows; 2 3import android.content.ContentValues; 4import android.database.Cursor; 5import android.database.sqlite.SQLiteDatabase; 6import com.xtremelabs.robolectric.Robolectric; 7import com.xtremelabs.robolectric.WithTestDefaultsRunner; 8import org.junit.After; 9import org.junit.Before; 10import org.junit.Test; 11import org.junit.runner.RunWith; 12 13import java.sql.ResultSet; 14import java.sql.SQLException; 15import java.sql.Statement; 16 17import static com.xtremelabs.robolectric.Robolectric.shadowOf; 18import static org.hamcrest.CoreMatchers.equalTo; 19import static org.hamcrest.CoreMatchers.not; 20import static org.junit.Assert.*; 21 22 23public abstract class DatabaseTestBase { 24 protected SQLiteDatabase database; 25 protected ShadowSQLiteDatabase shDatabase; 26 27 @Before 28 public void setUp() throws Exception { 29 database = SQLiteDatabase.openDatabase("path", null, 0); 30 shDatabase = Robolectric.shadowOf(database); 31 database.execSQL("CREATE TABLE table_name (\n" + 32 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 33 " first_column VARCHAR(255),\n" + 34 " second_column BINARY,\n" + 35 " name VARCHAR(255),\n" + 36 " big_int INTEGER\n" + 37 ");"); 38 39 database.execSQL("CREATE TABLE rawtable (\n" + 40 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 41 " first_column VARCHAR(255),\n" + 42 " second_column BINARY,\n" + 43 " name VARCHAR(255),\n" + 44 " big_int INTEGER\n" + 45 ");"); 46 47 database.execSQL("CREATE TABLE exectable (\n" + 48 " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" + 49 " first_column VARCHAR(255),\n" + 50 " second_column BINARY,\n" + 51 " name VARCHAR(255),\n" + 52 " big_int INTEGER\n" + 53 ");"); 54 55 String stringColumnValue = "column_value"; 56 byte[] byteColumnValue = new byte[]{1, 2, 3}; 57 58 ContentValues values = new ContentValues(); 59 60 values.put("first_column", stringColumnValue); 61 values.put("second_column", byteColumnValue); 62 63 database.insert("rawtable", null, values); 64 //////////////////////////////////////////////// 65 String stringColumnValue2 = "column_value2"; 66 byte[] byteColumnValue2 = new byte[]{4, 5, 6}; 67 ContentValues values2 = new ContentValues(); 68 69 values2.put("first_column", stringColumnValue2); 70 values2.put("second_column", byteColumnValue2); 71 72 database.insert("rawtable", null, values2); 73 } 74 75 76 @After 77 public void tearDown() throws Exception { 78 database.close(); 79 } 80 81 @Test() 82 public void testInsertAndQuery() throws Exception { 83 String stringColumnValue = "column_value"; 84 byte[] byteColumnValue = new byte[]{1, 2, 3}; 85 86 ContentValues values = new ContentValues(); 87 88 values.put("first_column", stringColumnValue); 89 values.put("second_column", byteColumnValue); 90 91 database.insert("table_name", null, values); 92 93 Cursor cursor = database.query("table_name", new String[]{"second_column", "first_column"}, null, null, null, null, null); 94 95 assertThat(cursor.moveToFirst(), equalTo(true)); 96 97 byte[] byteValueFromDatabase = cursor.getBlob(0); 98 String stringValueFromDatabase = cursor.getString(1); 99 100 assertThat(stringValueFromDatabase, equalTo(stringColumnValue)); 101 assertThat(byteValueFromDatabase, equalTo(byteColumnValue)); 102 } 103 104 @Test 105 public void testInsertAndRawQuery() throws Exception { 106 String stringColumnValue = "column_value"; 107 byte[] byteColumnValue = new byte[]{1, 2, 3}; 108 109 ContentValues values = new ContentValues(); 110 111 values.put("first_column", stringColumnValue); 112 values.put("second_column", byteColumnValue); 113 114 database.insert("table_name", null, values); 115 116 Cursor cursor = database.rawQuery("select second_column, first_column from table_name", null); 117 118 assertThat(cursor.moveToFirst(), equalTo(true)); 119 120 byte[] byteValueFromDatabase = cursor.getBlob(0); 121 String stringValueFromDatabase = cursor.getString(1); 122 123 assertThat(stringValueFromDatabase, equalTo(stringColumnValue)); 124 assertThat(byteValueFromDatabase, equalTo(byteColumnValue)); 125 } 126 127 @Test(expected = android.database.SQLException.class) 128 public void testInsertOrThrowWithSQLException() { 129 shDatabase.setThrowOnInsert(true); 130 database.insertOrThrow("table_name", null, new ContentValues()); 131 } 132 133 @Test 134 public void testInsertOrThrow() { 135 String stringColumnValue = "column_value"; 136 byte[] byteColumnValue = new byte[]{1, 2, 3}; 137 ContentValues values = new ContentValues(); 138 values.put("first_column", stringColumnValue); 139 values.put("second_column", byteColumnValue); 140 database.insertOrThrow("table_name", null, values); 141 142 Cursor cursor = database.rawQuery("select second_column, first_column from table_name", null); 143 assertThat(cursor.moveToFirst(), equalTo(true)); 144 byte[] byteValueFromDatabase = cursor.getBlob(0); 145 String stringValueFromDatabase = cursor.getString(1); 146 assertThat(stringValueFromDatabase, equalTo(stringColumnValue)); 147 assertThat(byteValueFromDatabase, equalTo(byteColumnValue)); 148 } 149 150 @Test(expected = IllegalArgumentException.class) 151 public void testRawQueryThrowsIndex0NullException() throws Exception { 152 database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{null}); 153 } 154 155 @Test(expected = IllegalArgumentException.class) 156 public void testRawQueryThrowsIndex0NullException2() throws Exception { 157 database.rawQuery("select second_column, first_column from rawtable", new String[]{null}); 158 } 159 160 @Test 161 public void testRawQueryCount() throws Exception { 162 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{"1"}); 163 assertThat(cursor.getCount(), equalTo(1)); 164 } 165 166 @Test 167 public void testRawQueryCount2() throws Exception { 168 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", null); 169 assertThat(cursor.getCount(), equalTo(2)); 170 } 171 172 @Test 173 public void testRawQueryCount3() throws Exception { 174 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", new String[]{}); 175 assertThat(cursor.getCount(), equalTo(2)); 176 } 177 /* 178 * Reason why testRawQueryCount4() and testRawQueryCount5() expects exceptions even though exceptions are not found in Android. 179 * 180 * The code in Android acts inconsistently under API version 2.1_r1 (and perhaps other APIs).. 181 * What happens is that rawQuery() remembers the selectionArgs of previous queries, 182 * and uses them if no selectionArgs are given in subsequent queries. 183 * If they were never given selectionArgs THEN they return empty cursors. 184 * 185 * 186 * if you run { 187 * db.rawQuery("select * from exercise WHERE name = ?",null); //this returns an empty cursor 188 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{}); //this returns an empty cursor 189 * } 190 * 191 * but if you run { 192 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{"Leg Press"}); //this returns 1 exercise named "Leg Press" 193 * db.rawQuery("select * from exercise WHERE name = ?",null); //this too returns 1 exercise named "Leg Press" 194 * db.rawQuery("select * from exercise WHERE name = ?",new String[]{}); //this too returns 1 exercise named "Leg Press" 195 * } 196 * 197 * so SQLite + Android work inconsistently (it maintains state that it should not) 198 * whereas H2 just throws an exception for not supplying the selectionArgs 199 * 200 * So the question is should Robolectric: 201 * 1) throw an exception, the way H2 does. 202 * 2) return an empty Cursor. 203 * 3) mimic Android\SQLite precisely and return inconsistent results based on previous state 204 * 205 * Returning an empty cursor all the time would be bad 206 * because Android doesn't always return an empty cursor. 207 * But just mimicing Android would not be helpful, 208 * since it would be less than obvious where the problem is coming from. 209 * One should just avoid ever calling a statement without selectionArgs (when one has a ? placeholder), 210 * so it is best to throw an Exception to let the programmer know that this isn't going to turn out well if they try to run it under Android. 211 * Because we are running in the context of a test we do not have to mimic Android precisely (if it is more helpful not to!), we just need to help 212 * the testing programmer figure out what is going on. 213 */ 214 215 @Test(expected = Exception.class) 216 public void testRawQueryCount4() throws Exception { 217 //Android and SQLite don't normally throw an exception here. See above explanation as to why Robolectric should. 218 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", null); 219 } 220 221 @Test(expected = Exception.class) 222 public void testRawQueryCount5() throws Exception { 223 //Android and SQLite don't normally throw an exception here. See above explanation as to why Robolectric should. 224 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable WHERE `id` = ?", new String[]{}); 225 } 226 227 @Test(expected = android.database.sqlite.SQLiteException.class) 228 public void testRawQueryCount8() throws Exception { 229 Cursor cursor = database.rawQuery("select second_column, first_column from rawtable", new String[]{"1"}); 230 } 231 232 @Test 233 public void testInsertWithException() { 234 ContentValues values = new ContentValues(); 235 236 assertEquals(-1, database.insert("table_that_doesnt_exist", null, values)); 237 } 238 239 240 @Test 241 public void testEmptyTable() throws Exception { 242 Cursor cursor = database.query("table_name", new String[]{"second_column", "first_column"}, null, null, null, null, null); 243 244 assertThat(cursor.moveToFirst(), equalTo(false)); 245 } 246 247 @Test 248 public void testInsertRowIdGeneration() throws Exception { 249 ContentValues values = new ContentValues(); 250 values.put("name", "Chuck"); 251 252 long id = database.insert("table_name", null, values); 253 254 assertThat(id, not(equalTo(0L))); 255 } 256 257 @Test 258 public void testInsertKeyGeneration() throws Exception { 259 ContentValues values = new ContentValues(); 260 values.put("name", "Chuck"); 261 262 long key = database.insertWithOnConflict("table_name", null, values, SQLiteDatabase.CONFLICT_IGNORE); 263 264 assertThat(key, not(equalTo(0L))); 265 } 266 267 @Test 268 public void testUpdate() throws Exception { 269 addChuck(); 270 271 assertThat(updateName(1234L, "Buster"), equalTo(1)); 272 273 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 274 assertThat(cursor.moveToFirst(), equalTo(true)); 275 assertThat(cursor.getCount(), equalTo(1)); 276 277 assertIdAndName(cursor, 1234L, "Buster"); 278 } 279 280 @Test 281 public void testUpdateNoMatch() throws Exception { 282 addChuck(); 283 284 assertThat(updateName(5678L, "Buster"), equalTo(0)); 285 286 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 287 assertThat(cursor.moveToFirst(), equalTo(true)); 288 assertThat(cursor.getCount(), equalTo(1)); 289 290 assertIdAndName(cursor, 1234L, "Chuck"); 291 } 292 293 @Test 294 public void testUpdateAll() throws Exception { 295 addChuck(); 296 addJulie(); 297 298 assertThat(updateName("Belvedere"), equalTo(2)); 299 300 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 301 assertThat(cursor.moveToFirst(), equalTo(true)); 302 assertThat(cursor.getCount(), equalTo(2)); 303 304 assertIdAndName(cursor, 1234L, "Belvedere"); 305 assertThat(cursor.moveToNext(), equalTo(true)); 306 307 assertIdAndName(cursor, 1235L, "Belvedere"); 308 assertThat(cursor.isLast(), equalTo(true)); 309 assertThat(cursor.moveToNext(), equalTo(false)); 310 assertThat(cursor.isAfterLast(), equalTo(true)); 311 assertThat(cursor.moveToNext(), equalTo(false)); 312 } 313 314 @Test 315 public void testDelete() throws Exception { 316 addChuck(); 317 318 int deleted = database.delete("table_name", "id=1234", null); 319 assertThat(deleted, equalTo(1)); 320 321 assertEmptyDatabase(); 322 } 323 324 @Test 325 public void testDeleteNoMatch() throws Exception { 326 addChuck(); 327 328 int deleted = database.delete("table_name", "id=5678", null); 329 assertThat(deleted, equalTo(0)); 330 331 assertNonEmptyDatabase(); 332 } 333 334 @Test 335 public void testDeleteAll() throws Exception { 336 addChuck(); 337 addJulie(); 338 339 int deleted = database.delete("table_name", "1", null); 340 assertThat(deleted, equalTo(2)); 341 342 assertEmptyDatabase(); 343 } 344 345 346 @Test 347 public void testExecSQL() throws Exception { 348 Statement statement; 349 ResultSet resultSet; 350 351 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 352 353 statement = shadowOf(database).getConnection().createStatement(); 354 resultSet = statement.executeQuery("SELECT COUNT(*) FROM table_name"); 355 assertThat(resultSet.next(), equalTo(true)); 356 assertThat(resultSet.getInt(1), equalTo(1)); 357 358 statement = shadowOf(database).getConnection().createStatement(); 359 resultSet = statement.executeQuery("SELECT * FROM table_name"); 360 assertThat(resultSet.next(), equalTo(true)); 361 assertThat(resultSet.getInt(1), equalTo(1234)); 362 assertThat(resultSet.getString(4), equalTo("Chuck")); 363 } 364 365 @Test 366 public void testExecSQLParams() throws Exception { 367 Statement statement; 368 ResultSet resultSet; 369 370 database.execSQL("CREATE TABLE `routine` (`id` INTEGER PRIMARY KEY AUTOINCREMENT , `name` VARCHAR , `lastUsed` INTEGER DEFAULT 0 , UNIQUE (`name`)) ", new Object[]{}); 371 database.execSQL("INSERT INTO `routine` (`name` ,`lastUsed` ) VALUES (?,?)", new Object[]{"Leg Press", 0}); 372 database.execSQL("INSERT INTO `routine` (`name` ,`lastUsed` ) VALUES (?,?)", new Object[]{"Bench Press", 1}); 373 374 statement = shadowOf(database).getConnection().createStatement(); 375 resultSet = statement.executeQuery("SELECT COUNT(*) FROM `routine`"); 376 assertThat(resultSet.next(), equalTo(true)); 377 assertThat(resultSet.getInt(1), equalTo(2)); 378 379 statement = shadowOf(database).getConnection().createStatement(); 380 resultSet = statement.executeQuery("SELECT `id`, `name` ,`lastUsed` FROM `routine`"); 381 assertThat(resultSet.next(), equalTo(true)); 382 assertThat(resultSet.getInt(1), equalTo(1)); 383 assertThat(resultSet.getString(2), equalTo("Leg Press")); 384 assertThat(resultSet.getInt(3), equalTo(0)); 385 assertThat(resultSet.next(), equalTo(true)); 386 assertThat(resultSet.getLong(1), equalTo(2L)); 387 assertThat(resultSet.getString(2), equalTo("Bench Press")); 388 assertThat(resultSet.getInt(3), equalTo(1)); 389 } 390 391 @Test(expected = android.database.SQLException.class) 392 public void testExecSQLException() throws Exception { 393 database.execSQL("INSERT INTO table_name;"); // invalid SQL 394 } 395 396 @Test(expected = IllegalArgumentException.class) 397 public void testExecSQLException2() throws Exception { 398 database.execSQL("insert into exectable (first_column) values (?);", null); 399 } 400 401 @Test(expected = IllegalArgumentException.class) 402 public void testExecSQLException4() throws Exception { 403 database.execSQL("insert into exectable (first_column) values ('sdfsfs');", null); 404 } 405 406 @Test(expected = Exception.class) 407 public void testExecSQLException5() throws Exception { 408 //TODO: make this throw android.database.SQLException.class 409 database.execSQL("insert into exectable (first_column) values ('kjhk');", new String[]{"xxxx"}); 410 } 411 412 @Test(expected = Exception.class) 413 public void testExecSQLException6() throws Exception { 414 //TODO: make this throw android.database.SQLException.class 415 database.execSQL("insert into exectable (first_column) values ('kdfd');", new String[]{null}); 416 } 417 418 @Test 419 public void testExecSQL2() throws Exception { 420 database.execSQL("insert into exectable (first_column) values ('eff');", new String[]{}); 421 } 422 423 @Test 424 public void testExecSQLInsertNull() throws Exception { 425 String name = "nullone"; 426 427 database.execSQL("insert into exectable (first_column, name) values (?,?);", new String[]{null, name}); 428 429 Cursor cursor = database.rawQuery("select * from exectable WHERE `name` = ?", new String[]{name}); 430 cursor.moveToFirst(); 431 int firstIndex = cursor.getColumnIndex("first_column"); 432 int nameIndex = cursor.getColumnIndex("name"); 433 assertThat(cursor.getString(nameIndex), equalTo(name)); 434 assertThat(cursor.getString(firstIndex), equalTo(null)); 435 436 } 437 438 @Test(expected = Exception.class) 439 public void testExecSQLInsertNullShouldBeException() throws Exception { 440 //this inserts null in android, but it when it happens it is likely an error. H2 throws an exception. So we'll make Robolectric expect an Exception so that the error can be found. 441 442 database.delete("exectable", null, null); 443 444 Cursor cursor = database.rawQuery("select * from exectable", null); 445 cursor.moveToFirst(); 446 assertThat(cursor.getCount(), equalTo(0)); 447 448 database.execSQL("insert into exectable (first_column) values (?);", new String[]{}); 449 Cursor cursor2 = database.rawQuery("select * from exectable", new String[]{null}); 450 cursor.moveToFirst(); 451 assertThat(cursor2.getCount(), equalTo(1)); 452 453 } 454 455 @Test 456 public void testExecSQLAutoIncrementSQLite() throws Exception { 457 database.execSQL("CREATE TABLE auto_table (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(255));"); 458 459 ContentValues values = new ContentValues(); 460 values.put("name", "Chuck"); 461 462 long key = database.insert("auto_table", null, values); 463 assertThat(key, not(equalTo(0L))); 464 465 long key2 = database.insert("auto_table", null, values); 466 assertThat(key2, not(equalTo(key))); 467 } 468 469 @Test(expected = IllegalStateException.class) 470 public void testClose() throws Exception { 471 database.close(); 472 473 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 474 } 475 476 @Test 477 public void testIsOpen() throws Exception { 478 assertThat(database.isOpen(), equalTo(true)); 479 database.close(); 480 assertThat(database.isOpen(), equalTo(false)); 481 } 482 483 @Test 484 public void shouldStoreGreatBigHonkinIntegersCorrectly() throws Exception { 485 database.execSQL("INSERT INTO table_name(big_int) VALUES(1234567890123456789);"); 486 Cursor cursor = database.query("table_name", new String[]{"big_int"}, null, null, null, null, null); 487 cursor.moveToFirst(); 488 assertEquals(1234567890123456789L, cursor.getLong(0)); 489 } 490 491 @Test 492 public void testSuccessTransaction() throws SQLException { 493 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 494 database.beginTransaction(); 495 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 496 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 497 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 498 database.setTransactionSuccessful(); 499 assertThat(shDatabase.isTransactionSuccess(), equalTo(true)); 500 database.endTransaction(); 501 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 502 503 Statement statement = shadowOf(database).getConnection().createStatement(); 504 ResultSet resultSet = statement.executeQuery("SELECT COUNT(*) FROM table_name"); 505 assertThat(resultSet.next(), equalTo(true)); 506 assertThat(resultSet.getInt(1), equalTo(1)); 507 } 508 509 @Test 510 public void testFailureTransaction() throws Exception { 511 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 512 database.beginTransaction(); 513 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 514 515 database.execSQL("INSERT INTO table_name (id, name) VALUES(1234, 'Chuck');"); 516 517 Statement statement = shadowOf(database).getConnection().createStatement(); 518 final String select = "SELECT COUNT(*) FROM table_name"; 519 520 ResultSet rs = statement.executeQuery(select); 521 assertThat(rs.next(), equalTo(true)); 522 assertThat(rs.getInt(1), equalTo(1)); 523 rs.close(); 524 525 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 526 database.endTransaction(); 527 528 statement = shadowOf(database).getConnection().createStatement(); 529 rs = statement.executeQuery(select); 530 assertThat(rs.next(), equalTo(true)); 531 assertThat(rs.getInt(1), equalTo(0)); 532 533 assertThat(shDatabase.isTransactionSuccess(), equalTo(false)); 534 } 535 536 @Test 537 public void testTransactionAlreadySuccessful() { 538 database.beginTransaction(); 539 database.setTransactionSuccessful(); 540 try { 541 database.setTransactionSuccessful(); 542 fail("didn't receive the expected IllegalStateException"); 543 } catch (IllegalStateException e) { 544 assertThat(e.getMessage(), equalTo("transaction already successfully")); 545 } 546 } 547 548 @Test 549 public void testInTransaction() throws Exception { 550 assertThat( database.inTransaction(), equalTo(false) ); 551 database.beginTransaction(); 552 assertThat( database.inTransaction(), equalTo(true) ); 553 database.endTransaction(); 554 assertThat( database.inTransaction(), equalTo(false) ); 555 } 556 557 protected long addChuck() { 558 return addPerson(1234L, "Chuck"); 559 } 560 561 protected long addJulie() { 562 return addPerson(1235L, "Julie"); 563 } 564 565 protected long addPerson(long id, String name) { 566 ContentValues values = new ContentValues(); 567 values.put("id", id); 568 values.put("name", name); 569 return database.insert("table_name", null, values); 570 } 571 572 protected int updateName(long id, String name) { 573 ContentValues values = new ContentValues(); 574 values.put("name", name); 575 return database.update("table_name", values, "id=" + id, null); 576 } 577 578 protected int updateName(String name) { 579 ContentValues values = new ContentValues(); 580 values.put("name", name); 581 return database.update("table_name", values, null, null); 582 } 583 584 protected void assertIdAndName(Cursor cursor, long id, String name) { 585 long idValueFromDatabase; 586 String stringValueFromDatabase; 587 588 idValueFromDatabase = cursor.getLong(0); 589 stringValueFromDatabase = cursor.getString(1); 590 assertThat(idValueFromDatabase, equalTo(id)); 591 assertThat(stringValueFromDatabase, equalTo(name)); 592 } 593 594 protected void assertEmptyDatabase() { 595 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 596 assertThat(cursor.moveToFirst(), equalTo(false)); 597 assertThat(cursor.isClosed(), equalTo(false)); 598 assertThat(cursor.getCount(), equalTo(0)); 599 } 600 601 protected void assertNonEmptyDatabase() { 602 Cursor cursor = database.query("table_name", new String[]{"id", "name"}, null, null, null, null, null); 603 assertThat(cursor.moveToFirst(), equalTo(true)); 604 assertThat(cursor.getCount(), not(equalTo(0))); 605 } 606} 607