Home | History | Annotate | Download | only in mocks
      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.android.contacts.test.mocks;
     18 
     19 import android.content.ContentValues;
     20 import android.content.UriMatcher;
     21 import android.database.Cursor;
     22 import android.database.MatrixCursor;
     23 import android.net.Uri;
     24 import android.support.annotation.Nullable;
     25 
     26 import com.google.common.base.Preconditions;
     27 import com.google.common.collect.Maps;
     28 
     29 import junit.framework.Assert;
     30 
     31 import java.util.ArrayList;
     32 import java.util.Arrays;
     33 import java.util.Iterator;
     34 import java.util.List;
     35 import java.util.Map;
     36 import java.util.Objects;
     37 
     38 /**
     39  * A programmable mock content provider.
     40  */
     41 public class MockContentProvider extends android.test.mock.MockContentProvider {
     42     private static final String TAG = "MockContentProvider";
     43 
     44     public static class Query {
     45 
     46         private final Uri mUri;
     47         private UriMatcher mMatcher;
     48 
     49         private String[] mProjection;
     50         private String[] mDefaultProjection;
     51         private String mSelection;
     52         private String[] mSelectionArgs;
     53         private String mSortOrder;
     54         private List<Object> mRows = new ArrayList<>();
     55         private boolean mAnyProjection;
     56         private boolean mAnySelection;
     57         private boolean mAnySortOrder;
     58         private boolean mAnyNumberOfTimes;
     59 
     60         private boolean mExecuted;
     61 
     62         private Query() {
     63             mUri = null;
     64         }
     65 
     66         private Query(UriMatcher matcher) {
     67             mUri = null;
     68             mMatcher = matcher;
     69         }
     70 
     71         public Query(Uri uri) {
     72             mUri = uri;
     73         }
     74 
     75         @Override
     76         public String toString() {
     77             return queryToString(mUri, mProjection, mSelection, mSelectionArgs, mSortOrder);
     78         }
     79 
     80         public Query withProjection(String... projection) {
     81             mProjection = projection;
     82             return this;
     83         }
     84 
     85         public Query withDefaultProjection(String... projection) {
     86             mDefaultProjection = projection;
     87             return this;
     88         }
     89 
     90         public Query withAnyProjection() {
     91             mAnyProjection = true;
     92             return this;
     93         }
     94 
     95         public Query withSelection(String selection, String... selectionArgs) {
     96             mSelection = selection;
     97             mSelectionArgs = selectionArgs;
     98             return this;
     99         }
    100 
    101         public Query withAnySelection() {
    102             mAnySelection = true;
    103             return this;
    104         }
    105 
    106         public Query withSortOrder(String sortOrder) {
    107             mSortOrder = sortOrder;
    108             return this;
    109         }
    110 
    111         public Query withAnySortOrder() {
    112             mAnySortOrder = true;
    113             return this;
    114         }
    115 
    116         public Query returnRow(ContentValues values) {
    117             mRows.add(values);
    118             return this;
    119         }
    120 
    121         public Query returnRow(Object... row) {
    122             mRows.add(row);
    123             return this;
    124         }
    125 
    126         public Query returnEmptyCursor() {
    127             mRows.clear();
    128             return this;
    129         }
    130 
    131         public Query anyNumberOfTimes() {
    132             mAnyNumberOfTimes = true;
    133             return this;
    134         }
    135 
    136         public boolean equals(Uri uri, String[] projection, String selection,
    137                 String[] selectionArgs, String sortOrder) {
    138             if (mUri == null) {
    139                 if (mMatcher != null && mMatcher.match(uri) == UriMatcher.NO_MATCH) {
    140                     return false;
    141                 }
    142             } else if (!uri.equals(mUri)) {
    143                 return false;
    144             }
    145 
    146             if (!mAnyProjection && !Arrays.equals(projection, mProjection)) {
    147                 return false;
    148             }
    149 
    150             if (!mAnySelection && !Objects.equals(selection, mSelection)) {
    151                 return false;
    152             }
    153 
    154             if (!mAnySelection && !Arrays.equals(selectionArgs, mSelectionArgs)) {
    155                 return false;
    156             }
    157 
    158             if (!mAnySortOrder && !Objects.equals(sortOrder, mSortOrder)) {
    159                 return false;
    160             }
    161 
    162             return true;
    163         }
    164 
    165         public Cursor getResult(String[] projection) {
    166             String[] columnNames;
    167             if (mAnyProjection) {
    168                 columnNames = projection != null ? projection : mDefaultProjection;
    169             } else {
    170                 columnNames = mProjection != null ? mProjection : mDefaultProjection;
    171             }
    172 
    173             MatrixCursor cursor = new MatrixCursor(columnNames);
    174             for (Object row : mRows) {
    175                 if (row instanceof Object[]) {
    176                     cursor.addRow((Object[]) row);
    177                 } else {
    178                     ContentValues values = (ContentValues) row;
    179                     Object[] columns = new Object[columnNames.length];
    180                     for (int i = 0; i < columnNames.length; i++) {
    181                         columns[i] = values.get(columnNames[i]);
    182                     }
    183                     cursor.addRow(columns);
    184                 }
    185             }
    186             return cursor;
    187         }
    188 
    189         public static Query forAnyUri() {
    190             return new Query();
    191         }
    192 
    193         public static Query forUrisMatching(UriMatcher matcher) {
    194             return new Query(matcher);
    195         }
    196 
    197         public static Query forUrisMatching(String authority, String... paths) {
    198             final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    199             for (int i = 0; i < paths.length; i++) {
    200                 matcher.addURI(authority, paths[i], i);
    201             }
    202             return new Query(matcher);
    203         }
    204 
    205     }
    206 
    207     public static class TypeQuery {
    208         private final Uri mUri;
    209         private final String mType;
    210 
    211         public TypeQuery(Uri uri, String type) {
    212             mUri = uri;
    213             mType = type;
    214         }
    215 
    216         public Uri getUri() {
    217             return mUri;
    218         }
    219 
    220         public String getType() {
    221             return mType;
    222         }
    223 
    224         @Override
    225         public String toString() {
    226             return mUri + " --> " + mType;
    227         }
    228 
    229         public boolean equals(Uri uri) {
    230             return getUri().equals(uri);
    231         }
    232     }
    233 
    234     public static class Insert {
    235         private final Uri mUri;
    236         private final ContentValues mContentValues;
    237         private final Uri mResultUri;
    238         private boolean mAnyNumberOfTimes;
    239         private boolean mIsExecuted;
    240 
    241         /**
    242          * Creates a new Insert to expect.
    243          *
    244          * @param uri the uri of the insertion request.
    245          * @param contentValues the ContentValues to insert.
    246          * @param resultUri the {@link Uri} for the newly inserted item.
    247          * @throws NullPointerException if any parameter is {@code null}.
    248          */
    249         public Insert(Uri uri, ContentValues contentValues, Uri resultUri) {
    250             mUri = Preconditions.checkNotNull(uri);
    251             mContentValues = Preconditions.checkNotNull(contentValues);
    252             mResultUri = Preconditions.checkNotNull(resultUri);
    253         }
    254 
    255         /**
    256          * Causes this insert expectation to be useable for mutliple calls to insert, rather than
    257          * just one.
    258          *
    259          * @return this
    260          */
    261         public Insert anyNumberOfTimes() {
    262             mAnyNumberOfTimes = true;
    263             return this;
    264         }
    265 
    266         private boolean equals(Uri uri, ContentValues contentValues) {
    267             return mUri.equals(uri) && mContentValues.equals(contentValues);
    268         }
    269 
    270         @Override
    271         public boolean equals(Object o) {
    272             if (this == o) {
    273                 return true;
    274             }
    275             if (o == null || getClass() != o.getClass()) {
    276                 return false;
    277             }
    278             Insert insert = (Insert) o;
    279             return mAnyNumberOfTimes == insert.mAnyNumberOfTimes &&
    280                     mIsExecuted == insert.mIsExecuted &&
    281                     Objects.equals(mUri, insert.mUri) &&
    282                     Objects.equals(mContentValues, insert.mContentValues) &&
    283                     Objects.equals(mResultUri, insert.mResultUri);
    284         }
    285 
    286         @Override
    287         public int hashCode() {
    288             return Objects.hash(mUri, mContentValues, mResultUri, mAnyNumberOfTimes, mIsExecuted);
    289         }
    290 
    291         @Override
    292         public String toString() {
    293             return "Insert{" +
    294                     "mUri=" + mUri +
    295                     ", mContentValues=" + mContentValues +
    296                     ", mResultUri=" + mResultUri +
    297                     ", mAnyNumberOfTimes=" + mAnyNumberOfTimes +
    298                     ", mIsExecuted=" + mIsExecuted +
    299                     '}';
    300         }
    301     }
    302 
    303     public static class Delete {
    304         private final Uri mUri;
    305 
    306         private boolean mAnyNumberOfTimes;
    307         private boolean mAnySelection;
    308         @Nullable private String mSelection;
    309         @Nullable private String[] mSelectionArgs;
    310         private boolean mIsExecuted;
    311         private int mRowsAffected;
    312 
    313         /**
    314          * Creates a new Delete to expect.
    315          * @param uri the uri of the delete request.
    316          * @throws NullPointerException if uri is {@code null}.
    317          */
    318         public Delete(Uri uri) {
    319             mUri = Preconditions.checkNotNull(uri);
    320         }
    321 
    322         /**
    323          * Sets the given information as expected selection arguments.
    324          *
    325          * @param selection The selection to expect.
    326          * @param selectionArgs The selection args to expect.
    327          * @return this.
    328          */
    329         public Delete withSelection(String selection, @Nullable String[] selectionArgs) {
    330             mSelection = Preconditions.checkNotNull(selection);
    331             mSelectionArgs = selectionArgs;
    332             mAnySelection = false;
    333             return this;
    334         }
    335 
    336         /**
    337          * Sets this delete to expect any selection arguments.
    338          *
    339          * @return this.
    340          */
    341         public Delete withAnySelection() {
    342             mAnySelection = true;
    343             return this;
    344         }
    345 
    346         /**
    347          * Sets this delete to return the given number of rows affected.
    348          *
    349          * @param rowsAffected The value to return when this expected delete is executed.
    350          * @return this.
    351          */
    352         public Delete returnRowsAffected(int rowsAffected) {
    353             mRowsAffected = rowsAffected;
    354             return this;
    355         }
    356 
    357         /**
    358          * Causes this delete expectation to be useable for multiple calls to delete, rather than
    359          * just one.
    360          *
    361          * @return this.
    362          */
    363         public Delete anyNumberOfTimes() {
    364             mAnyNumberOfTimes = true;
    365             return this;
    366         }
    367 
    368         private boolean equals(Uri uri, String selection, String[] selectionArgs) {
    369             return mUri.equals(uri) && Objects.equals(mSelection, selection)
    370                     && Arrays.equals(mSelectionArgs, selectionArgs);
    371         }
    372     }
    373 
    374     public static class Update {
    375         private final Uri mUri;
    376         private final ContentValues mContentValues;
    377         @Nullable private String mSelection;
    378         @Nullable private String[] mSelectionArgs;
    379         private boolean mAnyNumberOfTimes;
    380         private boolean mIsExecuted;
    381         private int mRowsAffected;
    382 
    383         /**
    384          * Creates a new Update to expect.
    385          *
    386          * @param uri the uri of the update request.
    387          * @param contentValues the ContentValues to update.
    388          *
    389          * @throws NullPointerException if any parameter is {@code null}.
    390          */
    391         public Update(Uri uri,
    392                       ContentValues contentValues,
    393                       @Nullable String selection,
    394                       @Nullable String[] selectionArgs) {
    395             mUri = Preconditions.checkNotNull(uri);
    396             mContentValues = Preconditions.checkNotNull(contentValues);
    397             mSelection = selection;
    398             mSelectionArgs = selectionArgs;
    399         }
    400 
    401         /**
    402          * Causes this update expectation to be useable for mutliple calls to update, rather than
    403          * just one.
    404          *
    405          * @return this
    406          */
    407         public Update anyNumberOfTimes() {
    408             mAnyNumberOfTimes = true;
    409             return this;
    410         }
    411 
    412         /**
    413          * Sets this update to return the given number of rows affected.
    414          *
    415          * @param rowsAffected The value to return when this expected update is executed.
    416          * @return this.
    417          */
    418         public Update returnRowsAffected(int rowsAffected) {
    419             mRowsAffected = rowsAffected;
    420             return this;
    421         }
    422 
    423         private boolean equals(Uri uri,
    424                                ContentValues contentValues,
    425                                @Nullable String selection,
    426                                @Nullable String[] selectionArgs) {
    427             return mUri.equals(uri) && mContentValues.equals(contentValues) &&
    428                     Objects.equals(mSelection, selection) &&
    429                     Arrays.equals(mSelectionArgs, selectionArgs);
    430         }
    431 
    432         @Override
    433         public boolean equals(Object o) {
    434             if (this == o) {
    435                 return true;
    436             }
    437             if (o == null || getClass() != o.getClass()) {
    438                 return false;
    439             }
    440             Update update = (Update) o;
    441             return mAnyNumberOfTimes == update.mAnyNumberOfTimes &&
    442                     mIsExecuted == update.mIsExecuted &&
    443                     Objects.equals(mUri, update.mUri) &&
    444                     Objects.equals(mContentValues, update.mContentValues) &&
    445                     Objects.equals(mSelection, update.mSelection) &&
    446                     Arrays.equals(mSelectionArgs, update.mSelectionArgs);
    447         }
    448 
    449         @Override
    450         public int hashCode() {
    451             return Objects.hash(mUri, mContentValues, mAnyNumberOfTimes, mIsExecuted, mSelection,
    452                     Arrays.hashCode(mSelectionArgs));
    453         }
    454 
    455         @Override
    456         public String toString() {
    457             return "Update{" +
    458                     "mUri=" + mUri +
    459                     ", mContentValues=" + mContentValues +
    460                     ", mAnyNumberOfTimes=" + mAnyNumberOfTimes +
    461                     ", mIsExecuted=" + mIsExecuted +
    462                     ", mSelection=" + mSelection +
    463                     ", mSelectionArgs=" + Arrays.toString(mSelectionArgs) +
    464                     '}';
    465         }
    466     }
    467 
    468     private List<Query> mExpectedQueries = new ArrayList<>();
    469     private Map<Uri, String> mExpectedTypeQueries = Maps.newHashMap();
    470     private List<Insert> mExpectedInserts = new ArrayList<>();
    471     private List<Delete> mExpectedDeletes = new ArrayList<>();
    472     private List<Update> mExpectedUpdates = new ArrayList<>();
    473 
    474     @Override
    475     public boolean onCreate() {
    476         return true;
    477     }
    478 
    479     public Query expect(Query query) {
    480         mExpectedQueries.add(query);
    481         return query;
    482     }
    483 
    484     public Query expectQuery(Uri contentUri) {
    485         return expect(new Query(contentUri));
    486     }
    487 
    488     public Query expectQuery(String contentUri) {
    489         return expectQuery(Uri.parse(contentUri));
    490     }
    491 
    492     public void expectTypeQuery(Uri uri, String type) {
    493         mExpectedTypeQueries.put(uri, type);
    494     }
    495 
    496     public void expectInsert(Uri contentUri, ContentValues contentValues, Uri resultUri) {
    497         mExpectedInserts.add(new Insert(contentUri, contentValues, resultUri));
    498     }
    499 
    500     public Update expectUpdate(Uri contentUri,
    501                                ContentValues contentValues,
    502                                @Nullable String selection,
    503                                @Nullable String[] selectionArgs) {
    504         Update update = new Update(contentUri, contentValues, selection, selectionArgs);
    505         mExpectedUpdates.add(update);
    506         return update;
    507     }
    508 
    509     public Delete expectDelete(Uri contentUri) {
    510         Delete delete = new Delete(contentUri);
    511         mExpectedDeletes.add(delete);
    512         return delete;
    513     }
    514 
    515     @Override
    516     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
    517             String sortOrder) {
    518         if (mExpectedQueries.isEmpty()) {
    519             Assert.fail("Unexpected query: Actual:"
    520                     + queryToString(uri, projection, selection, selectionArgs, sortOrder));
    521         }
    522 
    523         for (Iterator<Query> iterator = mExpectedQueries.iterator(); iterator.hasNext();) {
    524             Query query = iterator.next();
    525             if (query.equals(uri, projection, selection, selectionArgs, sortOrder)) {
    526                 query.mExecuted = true;
    527                 if (!query.mAnyNumberOfTimes) {
    528                     iterator.remove();
    529                 }
    530                 return query.getResult(projection);
    531             }
    532         }
    533 
    534         Assert.fail("Incorrect query. Expected one of: " + mExpectedQueries + ". Actual: " +
    535                 queryToString(uri, projection, selection, selectionArgs, sortOrder));
    536         return null;
    537     }
    538 
    539     @Override
    540     public String getType(Uri uri) {
    541         if (mExpectedTypeQueries.isEmpty()) {
    542             Assert.fail("Unexpected getType query: " + uri);
    543         }
    544 
    545         String mimeType = mExpectedTypeQueries.get(uri);
    546         if (mimeType != null) {
    547             return mimeType;
    548         }
    549 
    550         Assert.fail("Unknown mime type for: " + uri);
    551         return null;
    552     }
    553 
    554     @Override
    555     public Uri insert(Uri uri, ContentValues values) {
    556         if (mExpectedInserts.isEmpty()) {
    557             Assert.fail("Unexpected insert. Actual: " + insertToString(uri, values));
    558         }
    559         for (Iterator<Insert> iterator = mExpectedInserts.iterator(); iterator.hasNext(); ) {
    560             Insert insert = iterator.next();
    561             if (insert.equals(uri, values)) {
    562                 insert.mIsExecuted = true;
    563                 if (!insert.mAnyNumberOfTimes) {
    564                     iterator.remove();
    565                 }
    566                 return insert.mResultUri;
    567             }
    568         }
    569 
    570         Assert.fail("Incorrect insert. Expected one of: " + mExpectedInserts + ". Actual: "
    571                 + insertToString(uri, values));
    572         return null;
    573     }
    574 
    575     private String insertToString(Uri uri, ContentValues contentValues) {
    576         return "Insert { uri=" + uri + ", contentValues=" + contentValues + '}';
    577     }
    578 
    579     @Override
    580     public int update(Uri uri,
    581                       ContentValues values,
    582                       @Nullable String selection,
    583                       @Nullable String[] selectionArgs) {
    584         if (mExpectedUpdates.isEmpty()) {
    585             Assert.fail("Unexpected update. Actual: "
    586                     + updateToString(uri, values, selection, selectionArgs));
    587         }
    588         for (Iterator<Update> iterator = mExpectedUpdates.iterator(); iterator.hasNext(); ) {
    589             Update update = iterator.next();
    590             if (update.equals(uri, values, selection, selectionArgs)) {
    591                 update.mIsExecuted = true;
    592                 if (!update.mAnyNumberOfTimes) {
    593                     iterator.remove();
    594                 }
    595                 return update.mRowsAffected;
    596             }
    597         }
    598 
    599         Assert.fail("Incorrect update. Expected one of: " + mExpectedUpdates + ". Actual: "
    600                 + updateToString(uri, values, selection, selectionArgs));
    601         return - 1;
    602     }
    603 
    604     private String updateToString(Uri uri,
    605                                   ContentValues contentValues,
    606                                   @Nullable String selection,
    607                                   @Nullable String[] selectionArgs) {
    608         return "Update { uri=" + uri + ", contentValues=" + contentValues + ", selection=" +
    609                 selection + ", selectionArgs" + Arrays.toString(selectionArgs) + '}';
    610     }
    611 
    612     @Override
    613     public int delete(Uri uri, String selection, String[] selectionArgs) {
    614         if (mExpectedDeletes.isEmpty()) {
    615             Assert.fail("Unexpected delete. Actual: " + deleteToString(uri, selection,
    616                     selectionArgs));
    617         }
    618         for (Iterator<Delete> iterator = mExpectedDeletes.iterator(); iterator.hasNext(); ) {
    619             Delete delete = iterator.next();
    620             if (delete.equals(uri, selection, selectionArgs)) {
    621                 delete.mIsExecuted = true;
    622                 if (!delete.mAnyNumberOfTimes) {
    623                     iterator.remove();
    624                 }
    625                 return delete.mRowsAffected;
    626             }
    627         }
    628         Assert.fail("Incorrect delete. Expected one of: " + mExpectedDeletes + ". Actual: "
    629                 + deleteToString(uri, selection, selectionArgs));
    630         return -1;
    631     }
    632 
    633     private String deleteToString(Uri uri, String selection, String[] selectionArgs) {
    634         return "Delete { uri=" + uri + ", selection=" + selection + ", selectionArgs"
    635                 + Arrays.toString(selectionArgs) + '}';
    636     }
    637 
    638     private static String queryToString(Uri uri, String[] projection, String selection,
    639             String[] selectionArgs, String sortOrder) {
    640         StringBuilder sb = new StringBuilder();
    641         sb.append(uri == null ? "<Any Uri>" : uri).append(" ");
    642         if (projection != null) {
    643             sb.append(Arrays.toString(projection));
    644         } else {
    645             sb.append("[]");
    646         }
    647         if (selection != null) {
    648             sb.append(" selection: '").append(selection).append("'");
    649             if (selectionArgs != null) {
    650                 sb.append(Arrays.toString(selectionArgs));
    651             } else {
    652                 sb.append("[]");
    653             }
    654         }
    655         if (sortOrder != null) {
    656             sb.append(" sort: '").append(sortOrder).append("'");
    657         }
    658         return sb.toString();
    659     }
    660 
    661     public void verify() {
    662         verifyQueries();
    663         verifyInserts();
    664         verifyDeletes();
    665     }
    666 
    667     private void verifyQueries() {
    668         List<Query> missedQueries = new ArrayList<>();
    669         for (Query query : mExpectedQueries) {
    670             if (!query.mExecuted) {
    671                 missedQueries.add(query);
    672             }
    673         }
    674         Assert.assertTrue("Not all expected queries have been called: " + missedQueries,
    675                 missedQueries.isEmpty());
    676     }
    677 
    678     private void verifyInserts() {
    679         List<Insert> missedInserts = new ArrayList<>();
    680         for (Insert insert : mExpectedInserts) {
    681             if (!insert.mIsExecuted) {
    682                 missedInserts.add(insert);
    683             }
    684         }
    685         Assert.assertTrue("Not all expected inserts have been called: " + missedInserts,
    686                 missedInserts.isEmpty());
    687     }
    688 
    689     private void verifyDeletes() {
    690         List<Delete> missedDeletes = new ArrayList<>();
    691         for (Delete delete : mExpectedDeletes) {
    692             if (!delete.mIsExecuted) {
    693                 missedDeletes.add(delete);
    694             }
    695         }
    696         Assert.assertTrue("Not all expected deletes have been called: " + missedDeletes,
    697                 missedDeletes.isEmpty());
    698     }
    699 }
    700