1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.notepad; 18 19 import android.content.ContentUris; 20 import android.content.ContentValues; 21 import android.content.res.AssetFileDescriptor; 22 import android.database.Cursor; 23 import android.database.sqlite.SQLiteDatabase; 24 import android.net.Uri; 25 import android.os.ParcelFileDescriptor; 26 import android.test.ProviderTestCase2; 27 import android.test.mock.MockContentResolver; 28 29 import java.io.BufferedReader; 30 import java.io.FileDescriptor; 31 import java.io.FileNotFoundException; 32 import java.io.FileReader; 33 import java.io.IOException; 34 import java.util.Calendar; 35 import java.util.GregorianCalendar; 36 37 /* 38 */ 39 /** 40 * This class tests the content provider for the Note Pad sample application. 41 * 42 * To learn how to run an entire test package or one of its classes, please see 43 * "Testing in Eclipse, with ADT" or "Testing in Other IDEs" in the Developer Guide. 44 */ 45 public class NotePadProviderTest extends ProviderTestCase2<NotePadProvider> { 46 47 // A URI that the provider does not offer, for testing error handling. 48 private static final Uri INVALID_URI = 49 Uri.withAppendedPath(NotePad.Notes.CONTENT_URI, "invalid"); 50 51 // Contains a reference to the mocked content resolver for the provider under test. 52 private MockContentResolver mMockResolver; 53 54 // Contains an SQLite database, used as test data 55 private SQLiteDatabase mDb; 56 57 // Contains the test data, as an array of NoteInfo instances. 58 private final NoteInfo[] TEST_NOTES = { 59 new NoteInfo("Note0", "This is note 0"), 60 new NoteInfo("Note1", "This is note 1"), 61 new NoteInfo("Note2", "This is note 2"), 62 new NoteInfo("Note3", "This is note 3"), 63 new NoteInfo("Note4", "This is note 4"), 64 new NoteInfo("Note5", "This is note 5"), 65 new NoteInfo("Note6", "This is note 6"), 66 new NoteInfo("Note7", "This is note 7"), 67 new NoteInfo("Note8", "This is note 8"), 68 new NoteInfo("Note9", "This is note 9") }; 69 70 // Number of milliseconds in one day (milliseconds * seconds * minutes * hours) 71 private static final long ONE_DAY_MILLIS = 1000 * 60 * 60 * 24; 72 73 // Number of milliseconds in one week 74 private static final long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7; 75 76 // Creates a calendar object equal to January 1, 2010 at 12 midnight 77 private static final GregorianCalendar TEST_CALENDAR = 78 new GregorianCalendar(2010, Calendar.JANUARY, 1, 0, 0, 0); 79 80 // Stores a timestamp value, set to an arbitrary starting point 81 private final static long START_DATE = TEST_CALENDAR.getTimeInMillis(); 82 83 // Sets a MIME type filter, used to test provider methods that return more than one MIME type 84 // for a particular note. The filter will retrieve any MIME types supported for the content URI. 85 private final static String MIME_TYPES_ALL = "*/*"; 86 87 // Sets a MIME type filter, used to test provider methods that return more than one MIME type 88 // for a particular note. The filter is nonsense, so it will not retrieve any MIME types. 89 private final static String MIME_TYPES_NONE = "qwer/qwer"; 90 91 // Sets a MIME type filter for plain text, used to the provider's methods that only handle 92 // plain text 93 private final static String MIME_TYPE_TEXT = "text/plain"; 94 95 /* 96 * Constructor for the test case class. 97 * Calls the super constructor with the class name of the provider under test and the 98 * authority name of the provider. 99 */ NotePadProviderTest()100 public NotePadProviderTest() { 101 super(NotePadProvider.class, NotePad.AUTHORITY); 102 } 103 104 /* 105 * Sets up the test environment before each test method. Creates a mock content resolver, 106 * gets the provider under test, and creates a new database for the provider. 107 */ 108 @Override setUp()109 protected void setUp() throws Exception { 110 // Calls the base class implementation of this method. 111 super.setUp(); 112 113 // Gets the resolver for this test. 114 mMockResolver = getMockContentResolver(); 115 116 /* 117 * Gets a handle to the database underlying the provider. Gets the provider instance 118 * created in super.setUp(), gets the DatabaseOpenHelper for the provider, and gets 119 * a database object from the helper. 120 */ 121 mDb = getProvider().getOpenHelperForTest().getWritableDatabase(); 122 } 123 124 /* 125 * This method is called after each test method, to clean up the current fixture. Since 126 * this sample test case runs in an isolated context, no cleanup is necessary. 127 */ 128 @Override tearDown()129 protected void tearDown() throws Exception { 130 super.tearDown(); 131 } 132 133 /* 134 * Sets up test data. 135 * The test data is in an SQL database. It is created in setUp() without any data, 136 * and populated in insertData if necessary. 137 */ insertData()138 private void insertData() { 139 // Creates an instance of the ContentValues map type expected by database insertions 140 ContentValues values = new ContentValues(); 141 142 // Sets up test data 143 for (int index = 0; index < TEST_NOTES.length; index++) { 144 145 // Set the creation and modification date for the note 146 TEST_NOTES[index].setCreationDate(START_DATE + (index * ONE_DAY_MILLIS)); 147 TEST_NOTES[index].setModificationDate(START_DATE + (index * ONE_WEEK_MILLIS)); 148 149 // Adds a record to the database. 150 mDb.insertOrThrow( 151 NotePad.Notes.TABLE_NAME, // the table name for the insert 152 NotePad.Notes.COLUMN_NAME_TITLE, // column set to null if empty values map 153 TEST_NOTES[index].getContentValues() // the values map to insert 154 ); 155 } 156 } 157 158 /* 159 * Tests the provider's publicly available URIs. If the URI is not one that the provider 160 * understands, the provider should throw an exception. It also tests the provider's getType() 161 * method for each URI, which should return the MIME type associated with the URI. 162 */ testUriAndGetType()163 public void testUriAndGetType() { 164 // Tests the MIME type for the notes table URI. 165 String mimeType = mMockResolver.getType(NotePad.Notes.CONTENT_URI); 166 assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType); 167 168 // Tests the MIME type for the live folder URI. 169 mimeType = mMockResolver.getType(NotePad.Notes.LIVE_FOLDER_URI); 170 assertEquals(NotePad.Notes.CONTENT_TYPE, mimeType); 171 172 // Creates a URI with a pattern for note ids. The id doesn't have to exist. 173 Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1); 174 175 // Gets the note ID URI MIME type. 176 mimeType = mMockResolver.getType(noteIdUri); 177 assertEquals(NotePad.Notes.CONTENT_ITEM_TYPE, mimeType); 178 179 // Tests an invalid URI. This should throw an IllegalArgumentException. 180 mimeType = mMockResolver.getType(INVALID_URI); 181 } 182 183 /* 184 * Tests the provider's stream MIME types returned by getStreamTypes(). If the provider supports 185 * stream data for the URI, the MIME type is returned. Otherwise, the provider returns null. 186 */ testGetStreamTypes()187 public void testGetStreamTypes() { 188 189 // Tests the notes table URI. This should return null, since the content provider does 190 // not provide a stream MIME type for multiple notes. 191 assertNull(mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL)); 192 193 // Tests the live folders URI. This should return null, since the content provider does not 194 // provide a stream MIME type for multiple notes. 195 assertNull(mMockResolver.getStreamTypes(NotePad.Notes.LIVE_FOLDER_URI, MIME_TYPES_ALL)); 196 197 /* 198 * Tests the note id URI for a single note, using _ID value "1" which is a valid ID. Uses a 199 * valid MIME type filter that will return all the supported MIME types for a content URI. 200 * The result should be "text/plain". 201 */ 202 203 // Constructs the note id URI 204 Uri testUri = Uri.withAppendedPath(NotePad.Notes.CONTENT_ID_URI_BASE, "1"); 205 206 // Gets the MIME types for the URI, with the filter that selects all MIME types. 207 String mimeType[] = mMockResolver.getStreamTypes(testUri, MIME_TYPES_ALL); 208 209 // Tests that the result is not null and is equal to the expected value. Also tests that 210 // only one MIME type is returned. 211 assertNotNull(mimeType); 212 assertEquals(mimeType[0],"text/plain"); 213 assertEquals(mimeType.length,1); 214 215 /* 216 * Tests with the same URI but with a filter that should not return any URIs. 217 */ 218 mimeType = mMockResolver.getStreamTypes(testUri, MIME_TYPES_NONE); 219 assertNull(mimeType); 220 221 /* 222 * Tests with a URI that should not have any associated stream MIME types, but with a 223 * filter that returns all types. The result should still be null. 224 */ 225 mimeType = mMockResolver.getStreamTypes(NotePad.Notes.CONTENT_URI, MIME_TYPES_ALL); 226 assertNull(mimeType); 227 228 } 229 230 /* 231 * Tests the provider's public API for opening a read-only pipe of data for a note ID URI 232 * and MIME type filter matching "text/plain". 233 * This method throws a FileNotFoundException if the URI isn't for a note ID or the MIME type 234 * filter isn't "text/plain". It throws an IOException if it can't close a file descriptor. 235 */ testOpenTypedAssetFile()236 public void testOpenTypedAssetFile() throws FileNotFoundException, IOException { 237 238 // A URI to contain a note ID content URI. 239 Uri testNoteIdUri; 240 241 // A handle for the file descriptor returned by openTypedAssetFile(). 242 AssetFileDescriptor testAssetDescriptor; 243 244 // Inserts data into the provider, so that the note ID URI will be recognized. 245 insertData(); 246 247 // Constructs a URI with a note ID of 1. This matches the note ID URI pattern that 248 // openTypedAssetFile can handle. 249 testNoteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1); 250 251 // Opens the pipe. The opts argument is for passing options from a caller to the provider, 252 // but the NotePadProvider does not use it. 253 testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor( 254 testNoteIdUri, // the URI for a single note. The pipe points to this 255 // note's data 256 MIME_TYPE_TEXT, // a MIME type of "text/plain" 257 null // the "opts" argument 258 ); 259 260 // Gets the parcel file handle from the asset file handle. 261 ParcelFileDescriptor testParcelDescriptor = testAssetDescriptor.getParcelFileDescriptor(); 262 263 // Gets the file handle from the asset file handle. 264 FileDescriptor testDescriptor = testAssetDescriptor.getFileDescriptor(); 265 266 // Tests that the asset file handle is not null. 267 assertNotNull(testAssetDescriptor); 268 269 // Tests that the parcel file handle is not null. 270 assertNotNull(testParcelDescriptor); 271 272 // Tests that the file handle is not null. 273 assertNotNull(testDescriptor); 274 275 // Tests that the file handle is valid. 276 assertTrue(testDescriptor.valid()); 277 278 // Closes the file handles. 279 testParcelDescriptor.close(); 280 testAssetDescriptor.close(); 281 282 /* 283 * Changes the URI to a notes URI for multiple notes, and re-test. This should fail, since 284 * the provider does not support this type of URI. A FileNotFound exception is expected, 285 * so call fail() if it does *not* occur. 286 */ 287 try { 288 testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor( 289 NotePad.Notes.CONTENT_URI, 290 MIME_TYPE_TEXT, 291 null 292 ); 293 fail(); 294 } catch (FileNotFoundException e) { 295 // continue 296 } 297 298 /* 299 * Changes back to the note ID URI, but changes the MIME type filter to one that is not 300 * supported by the provider. This should also fail, since the provider will only open a 301 * pipe for MIME type "text/plain". A FileNotFound exception is expected, so calls 302 * fail() if it does *not* occur. 303 */ 304 305 try { 306 testAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor( 307 testNoteIdUri, 308 MIME_TYPES_NONE, 309 null 310 ); 311 fail(); 312 } catch (FileNotFoundException e) { 313 // continue 314 } 315 316 } 317 318 /* 319 * Tests the provider's method for actually returning writing data into a pipe. The method is 320 * writeDataToPipe, but this method is not called directly. Instead, a caller invokes 321 * openTypedAssetFile(). That method uses ContentProvider.openPipeHelper(), which has as one of 322 * its arguments a ContentProvider.PipeDataWriter object that must actually put the data into 323 * the pipe. PipeDataWriter is an interface, not a class, so it must be implemented. 324 * 325 * The NotePadProvider class itself implements the "ContentProvider.PipeDataWriter, which means 326 * that it supplies the interface's only method, writeDataToPipe(). In effect, a call to 327 * openTypedAssetFile() calls writeDataToPipe(). 328 * 329 * The test of writeDataToPipe() is separate from other tests of openTypedAssetFile() for the 330 * sake of clarity. 331 */ testWriteDataToPipe()332 public void testWriteDataToPipe() throws FileNotFoundException { 333 334 // A string array to hold the incoming data 335 String[] inputData = {"","",""}; 336 337 // A URI for a note ID. 338 Uri noteIdUri; 339 340 // A Cursor to contain the retrieved note. 341 Cursor noteIdCursor; 342 343 // An AssetFileDescriptor for the pipe. 344 AssetFileDescriptor noteIdAssetDescriptor; 345 346 // The ParcelFileDescriptor in the AssetFileDescriptor 347 ParcelFileDescriptor noteIdParcelDescriptor; 348 349 // Inserts test data into the provider. 350 insertData(); 351 352 // Creates note ID URI for a note that should now be in the provider. 353 noteIdUri = ContentUris.withAppendedId( 354 NotePad.Notes.CONTENT_ID_URI_BASE, // The base pattern for a note ID URI 355 1 // Sets the URI to point to record ID 1 in the 356 // provider 357 ); 358 359 // Gets a Cursor for the note. 360 noteIdCursor = mMockResolver.query( 361 noteIdUri, // the URI for the note ID we want to retrieve 362 null, // no projection, retrieve all the columns 363 null, // no WHERE clause 364 null, // no WHERE arguments 365 null // default sort order 366 ); 367 368 // Checks that the call worked. 369 // a) Checks that the cursor is not null 370 // b) Checks that it contains a single record 371 assertNotNull(noteIdCursor); 372 assertEquals(1,noteIdCursor.getCount()); 373 374 // Opens the pipe that will contain the data. 375 noteIdAssetDescriptor = mMockResolver.openTypedAssetFileDescriptor( 376 noteIdUri, // the URI of the note that will provide the data 377 MIME_TYPE_TEXT, // the "text/plain" MIME type 378 null // no other options 379 ); 380 381 // Checks that the call worked. 382 // a) checks that the AssetFileDescriptor is not null 383 // b) gets its ParcelFileDescriptor 384 // c) checks that the ParcelFileDescriptor is not null 385 assertNotNull(noteIdAssetDescriptor); 386 noteIdParcelDescriptor = noteIdAssetDescriptor.getParcelFileDescriptor(); 387 assertNotNull(noteIdParcelDescriptor); 388 389 // Gets a File Reader that can read the pipe. 390 FileReader fIn = new FileReader(noteIdParcelDescriptor.getFileDescriptor()); 391 392 // Gets a buffered reader wrapper for the File Reader. This allows reading line by line. 393 BufferedReader bIn = new BufferedReader(fIn); 394 395 /* 396 * The pipe should contain three lines: The note's title, an empty line, and the note's 397 * contents. The following code reads and stores these three lines. 398 */ 399 for (int index = 0; index < inputData.length; index++) { 400 try { 401 inputData[index] = bIn.readLine(); 402 } catch (IOException e) { 403 404 e.printStackTrace(); 405 fail(); 406 } 407 } 408 409 // Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same 410 // note title as the first line retrieved from the pipe. 411 assertEquals(TEST_NOTES[0].title, inputData[0]); 412 413 // Asserts that the first record in the provider (written from TEST_NOTES[0]) has the same 414 // note contents as the third line retrieved from the pipe. 415 assertEquals(TEST_NOTES[0].note, inputData[2]); 416 } 417 418 /* 419 * Tests the provider's public API for querying data in the table, using the URI for 420 * a dataset of records. 421 */ testQueriesOnNotesUri()422 public void testQueriesOnNotesUri() { 423 // Defines a projection of column names to return for a query 424 final String[] TEST_PROJECTION = { 425 NotePad.Notes.COLUMN_NAME_TITLE, 426 NotePad.Notes.COLUMN_NAME_NOTE, 427 NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE 428 }; 429 430 // Defines a selection column for the query. When the selection columns are passed 431 // to the query, the selection arguments replace the placeholders. 432 final String TITLE_SELECTION = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?"; 433 434 // Defines the selection columns for a query. 435 final String SELECTION_COLUMNS = 436 TITLE_SELECTION + " OR " + TITLE_SELECTION + " OR " + TITLE_SELECTION; 437 438 // Defines the arguments for the selection columns. 439 final String[] SELECTION_ARGS = { "Note0", "Note1", "Note5" }; 440 441 // Defines a query sort order 442 final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC"; 443 444 // Query subtest 1. 445 // If there are no records in the table, the returned cursor from a query should be empty. 446 Cursor cursor = mMockResolver.query( 447 NotePad.Notes.CONTENT_URI, // the URI for the main data table 448 null, // no projection, get all columns 449 null, // no selection criteria, get all records 450 null, // no selection arguments 451 null // use default sort order 452 ); 453 454 // Asserts that the returned cursor contains no records 455 assertEquals(0, cursor.getCount()); 456 457 // Query subtest 2. 458 // If the table contains records, the returned cursor from a query should contain records. 459 460 // Inserts the test data into the provider's underlying data source 461 insertData(); 462 463 // Gets all the columns for all the rows in the table 464 cursor = mMockResolver.query( 465 NotePad.Notes.CONTENT_URI, // the URI for the main data table 466 null, // no projection, get all columns 467 null, // no selection criteria, get all records 468 null, // no selection arguments 469 null // use default sort order 470 ); 471 472 // Asserts that the returned cursor contains the same number of rows as the size of the 473 // test data array. 474 assertEquals(TEST_NOTES.length, cursor.getCount()); 475 476 // Query subtest 3. 477 // A query that uses a projection should return a cursor with the same number of columns 478 // as the projection, with the same names, in the same order. 479 Cursor projectionCursor = mMockResolver.query( 480 NotePad.Notes.CONTENT_URI, // the URI for the main data table 481 TEST_PROJECTION, // get the title, note, and mod date columns 482 null, // no selection columns, get all the records 483 null, // no selection criteria 484 null // use default the sort order 485 ); 486 487 // Asserts that the number of columns in the cursor is the same as in the projection 488 assertEquals(TEST_PROJECTION.length, projectionCursor.getColumnCount()); 489 490 // Asserts that the names of the columns in the cursor and in the projection are the same. 491 // This also verifies that the names are in the same order. 492 assertEquals(TEST_PROJECTION[0], projectionCursor.getColumnName(0)); 493 assertEquals(TEST_PROJECTION[1], projectionCursor.getColumnName(1)); 494 assertEquals(TEST_PROJECTION[2], projectionCursor.getColumnName(2)); 495 496 // Query subtest 4 497 // A query that uses selection criteria should return only those rows that match the 498 // criteria. Use a projection so that it's easy to get the data in a particular column. 499 projectionCursor = mMockResolver.query( 500 NotePad.Notes.CONTENT_URI, // the URI for the main data table 501 TEST_PROJECTION, // get the title, note, and mod date columns 502 SELECTION_COLUMNS, // select on the title column 503 SELECTION_ARGS, // select titles "Note0", "Note1", or "Note5" 504 SORT_ORDER // sort ascending on the title column 505 ); 506 507 // Asserts that the cursor has the same number of rows as the number of selection arguments 508 assertEquals(SELECTION_ARGS.length, projectionCursor.getCount()); 509 510 int index = 0; 511 512 while (projectionCursor.moveToNext()) { 513 514 // Asserts that the selection argument at the current index matches the value of 515 // the title column (column 0) in the current record of the cursor 516 assertEquals(SELECTION_ARGS[index], projectionCursor.getString(0)); 517 518 index++; 519 } 520 521 // Asserts that the index pointer is now the same as the number of selection arguments, so 522 // that the number of arguments tested is exactly the same as the number of rows returned. 523 assertEquals(SELECTION_ARGS.length, index); 524 525 } 526 527 /* 528 * Tests queries against the provider, using the note id URI. This URI encodes a single 529 * record ID. The provider should only return 0 or 1 record. 530 */ testQueriesOnNoteIdUri()531 public void testQueriesOnNoteIdUri() { 532 // Defines the selection column for a query. The "?" is replaced by entries in the 533 // selection argument array 534 final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?"; 535 536 // Defines the argument for the selection column. 537 final String[] SELECTION_ARGS = { "Note1" }; 538 539 // A sort order for the query. 540 final String SORT_ORDER = NotePad.Notes.COLUMN_NAME_TITLE + " ASC"; 541 542 // Creates a projection includes the note id column, so that note id can be retrieved. 543 final String[] NOTE_ID_PROJECTION = { 544 NotePad.Notes._ID, // The Notes class extends BaseColumns, 545 // which includes _ID as the column name for the 546 // record's id in the data model 547 NotePad.Notes.COLUMN_NAME_TITLE}; // The note's title 548 549 // Query subtest 1. 550 // Tests that a query against an empty table returns null. 551 552 // Constructs a URI that matches the provider's notes id URI pattern, using an arbitrary 553 // value of 1 as the note ID. 554 Uri noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, 1); 555 556 // Queries the table with the notes ID URI. This should return an empty cursor. 557 Cursor cursor = mMockResolver.query( 558 noteIdUri, // URI pointing to a single record 559 null, // no projection, get all the columns for each record 560 null, // no selection criteria, get all the records in the table 561 null, // no need for selection arguments 562 null // default sort, by ascending title 563 ); 564 565 // Asserts that the cursor is null. 566 assertEquals(0,cursor.getCount()); 567 568 // Query subtest 2. 569 // Tests that a query against a table containing records returns a single record whose ID 570 // is the one requested in the URI provided. 571 572 // Inserts the test data into the provider's underlying data source. 573 insertData(); 574 575 // Queries the table using the URI for the full table. 576 cursor = mMockResolver.query( 577 NotePad.Notes.CONTENT_URI, // the base URI for the table 578 NOTE_ID_PROJECTION, // returns the ID and title columns of rows 579 SELECTION_COLUMNS, // select based on the title column 580 SELECTION_ARGS, // select title of "Note1" 581 SORT_ORDER // sort order returned is by title, ascending 582 ); 583 584 // Asserts that the cursor contains only one row. 585 assertEquals(1, cursor.getCount()); 586 587 // Moves to the cursor's first row, and asserts that this did not fail. 588 assertTrue(cursor.moveToFirst()); 589 590 // Saves the record's note ID. 591 int inputNoteId = cursor.getInt(0); 592 593 // Builds a URI based on the provider's content ID URI base and the saved note ID. 594 noteIdUri = ContentUris.withAppendedId(NotePad.Notes.CONTENT_ID_URI_BASE, inputNoteId); 595 596 // Queries the table using the content ID URI, which returns a single record with the 597 // specified note ID, matching the selection criteria provided. 598 cursor = mMockResolver.query(noteIdUri, // the URI for a single note 599 NOTE_ID_PROJECTION, // same projection, get ID and title columns 600 SELECTION_COLUMNS, // same selection, based on title column 601 SELECTION_ARGS, // same selection arguments, title = "Note1" 602 SORT_ORDER // same sort order returned, by title, ascending 603 ); 604 605 // Asserts that the cursor contains only one row. 606 assertEquals(1, cursor.getCount()); 607 608 // Moves to the cursor's first row, and asserts that this did not fail. 609 assertTrue(cursor.moveToFirst()); 610 611 // Asserts that the note ID passed to the provider is the same as the note ID returned. 612 assertEquals(inputNoteId, cursor.getInt(0)); 613 } 614 615 /* 616 * Tests inserts into the data model. 617 */ testInserts()618 public void testInserts() { 619 // Creates a new note instance with ID of 30. 620 NoteInfo note = new NoteInfo( 621 "Note30", // the note's title 622 "Test inserting a note" // the note's content 623 ); 624 625 // Sets the note's creation and modification times 626 note.setCreationDate(START_DATE + (10 * ONE_DAY_MILLIS)); 627 note.setModificationDate(START_DATE + (2 * ONE_WEEK_MILLIS)); 628 629 // Insert subtest 1. 630 // Inserts a row using the new note instance. 631 // No assertion will be done. The insert() method either works or throws an Exception 632 Uri rowUri = mMockResolver.insert( 633 NotePad.Notes.CONTENT_URI, // the main table URI 634 note.getContentValues() // the map of values to insert as a new record 635 ); 636 637 // Parses the returned URI to get the note ID of the new note. The ID is used in subtest 2. 638 long noteId = ContentUris.parseId(rowUri); 639 640 // Does a full query on the table. Since insertData() hasn't yet been called, the 641 // table should only contain the record just inserted. 642 Cursor cursor = mMockResolver.query( 643 NotePad.Notes.CONTENT_URI, // the main table URI 644 null, // no projection, return all the columns 645 null, // no selection criteria, return all the rows in the model 646 null, // no selection arguments 647 null // default sort order 648 ); 649 650 // Asserts that there should be only 1 record. 651 assertEquals(1, cursor.getCount()); 652 653 // Moves to the first (and only) record in the cursor and asserts that this worked. 654 assertTrue(cursor.moveToFirst()); 655 656 // Since no projection was used, get the column indexes of the returned columns 657 int titleIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_TITLE); 658 int noteIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_NOTE); 659 int crdateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_CREATE_DATE); 660 int moddateIndex = cursor.getColumnIndex(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE); 661 662 // Tests each column in the returned cursor against the data that was inserted, comparing 663 // the field in the NoteInfo object to the data at the column index in the cursor. 664 assertEquals(note.title, cursor.getString(titleIndex)); 665 assertEquals(note.note, cursor.getString(noteIndex)); 666 assertEquals(note.createDate, cursor.getLong(crdateIndex)); 667 assertEquals(note.modDate, cursor.getLong(moddateIndex)); 668 669 // Insert subtest 2. 670 // Tests that we can't insert a record whose id value already exists. 671 672 // Defines a ContentValues object so that the test can add a note ID to it. 673 ContentValues values = note.getContentValues(); 674 675 // Adds the note ID retrieved in subtest 1 to the ContentValues object. 676 values.put(NotePad.Notes._ID, (int) noteId); 677 678 // Tries to insert this record into the table. This should fail and drop into the 679 // catch block. If it succeeds, issue a failure message. 680 try { 681 rowUri = mMockResolver.insert(NotePad.Notes.CONTENT_URI, values); 682 fail("Expected insert failure for existing record but insert succeeded."); 683 } catch (Exception e) { 684 // succeeded, so do nothing. 685 } 686 } 687 688 /* 689 * Tests deletions from the data model. 690 */ testDeletes()691 public void testDeletes() { 692 // Subtest 1. 693 // Tries to delete a record from a data model that is empty. 694 695 // Sets the selection column to "title" 696 final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?"; 697 698 // Sets the selection argument "Note0" 699 final String[] SELECTION_ARGS = { "Note0" }; 700 701 // Tries to delete rows matching the selection criteria from the data model. 702 int rowsDeleted = mMockResolver.delete( 703 NotePad.Notes.CONTENT_URI, // the base URI of the table 704 SELECTION_COLUMNS, // select based on the title column 705 SELECTION_ARGS // select title = "Note0" 706 ); 707 708 // Assert that the deletion did not work. The number of deleted rows should be zero. 709 assertEquals(0, rowsDeleted); 710 711 // Subtest 2. 712 // Tries to delete an existing record. Repeats the previous subtest, but inserts data first. 713 714 // Inserts data into the model. 715 insertData(); 716 717 // Uses the same parameters to try to delete the row with title "Note0" 718 rowsDeleted = mMockResolver.delete( 719 NotePad.Notes.CONTENT_URI, // the base URI of the table 720 SELECTION_COLUMNS, // same selection column, "title" 721 SELECTION_ARGS // same selection arguments, title = "Note0" 722 ); 723 724 // The number of deleted rows should be 1. 725 assertEquals(1, rowsDeleted); 726 727 // Tests that the record no longer exists. Tries to get it from the table, and 728 // asserts that nothing was returned. 729 730 // Queries the table with the same selection column and argument used to delete the row. 731 Cursor cursor = mMockResolver.query( 732 NotePad.Notes.CONTENT_URI, // the base URI of the table 733 null, // no projection, return all columns 734 SELECTION_COLUMNS, // select based on the title column 735 SELECTION_ARGS, // select title = "Note0" 736 null // use the default sort order 737 ); 738 739 // Asserts that the cursor is empty since the record had already been deleted. 740 assertEquals(0, cursor.getCount()); 741 } 742 743 /* 744 * Tests updates to the data model. 745 */ testUpdates()746 public void testUpdates() { 747 // Selection column for identifying a record in the data model. 748 final String SELECTION_COLUMNS = NotePad.Notes.COLUMN_NAME_TITLE + " = " + "?"; 749 750 // Selection argument for the selection column. 751 final String[] selectionArgs = { "Note1" }; 752 753 // Defines a map of column names and values 754 ContentValues values = new ContentValues(); 755 756 // Subtest 1. 757 // Tries to update a record in an empty table. 758 759 // Sets up the update by putting the "note" column and a value into the values map. 760 values.put(NotePad.Notes.COLUMN_NAME_NOTE, "Testing an update with this string"); 761 762 // Tries to update the table 763 int rowsUpdated = mMockResolver.update( 764 NotePad.Notes.CONTENT_URI, // the URI of the data table 765 values, // a map of the updates to do (column title and value) 766 SELECTION_COLUMNS, // select based on the title column 767 selectionArgs // select "title = Note1" 768 ); 769 770 // Asserts that no rows were updated. 771 assertEquals(0, rowsUpdated); 772 773 // Subtest 2. 774 // Builds the table, and then tries the update again using the same arguments. 775 776 // Inserts data into the model. 777 insertData(); 778 779 // Does the update again, using the same arguments as in subtest 1. 780 rowsUpdated = mMockResolver.update( 781 NotePad.Notes.CONTENT_URI, // The URI of the data table 782 values, // the same map of updates 783 SELECTION_COLUMNS, // same selection, based on the title column 784 selectionArgs // same selection argument, to select "title = Note1" 785 ); 786 787 // Asserts that only one row was updated. The selection criteria evaluated to 788 // "title = Note1", and the test data should only contain one row that matches that. 789 assertEquals(1, rowsUpdated); 790 791 } 792 793 // A utility for converting note data to a ContentValues map. 794 private static class NoteInfo { 795 String title; 796 String note; 797 long createDate; 798 long modDate; 799 800 /* 801 * Constructor for a NoteInfo instance. This class helps create a note and 802 * return its values in a ContentValues map expected by data model methods. 803 * The note's id is created automatically when it is inserted into the data model. 804 */ NoteInfo(String t, String n)805 public NoteInfo(String t, String n) { 806 title = t; 807 note = n; 808 createDate = 0; 809 modDate = 0; 810 } 811 812 // Sets the creation date for a test note setCreationDate(long c)813 public void setCreationDate(long c) { 814 createDate = c; 815 } 816 817 // Sets the modification date for a test note setModificationDate(long m)818 public void setModificationDate(long m) { 819 modDate = m; 820 } 821 822 /* 823 * Returns a ContentValues instance (a map) for this NoteInfo instance. This is useful for 824 * inserting a NoteInfo into a database. 825 */ getContentValues()826 public ContentValues getContentValues() { 827 // Gets a new ContentValues object 828 ContentValues v = new ContentValues(); 829 830 // Adds map entries for the user-controlled fields in the map 831 v.put(NotePad.Notes.COLUMN_NAME_TITLE, title); 832 v.put(NotePad.Notes.COLUMN_NAME_NOTE, note); 833 v.put(NotePad.Notes.COLUMN_NAME_CREATE_DATE, createDate); 834 v.put(NotePad.Notes.COLUMN_NAME_MODIFICATION_DATE, modDate); 835 return v; 836 837 } 838 } 839 } 840