CursorMock

Additional

Language
Java
Version
N/A
Created
Dec 28, 2016
Updated
Dec 28, 2016 (Retired)
Owner
Dimitry (noties)
Contributor
Dimitry (noties)
1
Activity
Badge
Generate
Download
Source code

Announcement

CursorMock

CursorMock is a library for Android that helps testing SQLite related code without need to create testing databases, managing database connections, etc in/for testing enviroment. CursorMock gives ability to build a Cursor in a fast and reliable way from manually provided values or from data objects.

final Cursor cursor = CursorMockBuilder.forColumns("id", "first_name", "last_name")
        .addRow(1L, "Fu", "Bar")
        .addRow(2L, "Bar", "Fu")
        .build();
final Cursor cursor = CursorMockBuilder.forClass(MyClass.class)
        .add(new MyClass())
        .addAll(Arrays.asList(new MyClass(), new MyClass()))
        .build();

Installation

For production code

compile 'ru.noties:cursormock:1.0.0'

For JUnit testing (CursorMock works great with Robolectric)

testCompile 'ru.noties:cursormock:1.0.0'

For Android testing

androidTestCompile 'ru.noties:cursormock:1.0.0'

Basics

forColumns

final CursorMock mock = CursorMockBuilder.forColumns("id", "name", "points")
        // adding a new row with all values = null
        .addRow()
        // adding a new row with one value specified (at index 0)
        .addRow(2L)
        // adding a new row with first 2 values specified
        .addRow(3L, "FuBar")
        // adding a new row with only one value at index 2 specified
        .addRow(null, null, 34)
        // returns CursorMock instance (which implements Cursor)
        .build();

So, after build the data can be represented as a table:

* id name points
1 null null null
2 2L null null
3 3L FuBar null
4 null null 34

CursorMock enforces type safety, so one column can have only one type of the value for all rows. For example:

final CursorMock mock = CursorMockBuilder.forColumns("id")
        .addRow(1L)
        .addRow("string")
        .build();

will throw an exception at .addRow("string") as previously a long was added at that index.

Types

CursorMock has 4 types of the data that can be added to Cursor without explicit convertion (other types will be discussed further):

Java type Cursor
short INT
int INT
long INT
float FLOAT
double FLOAT
String TEXT
byte[] BLOB

All numeric types can be also boxed (Short, Integer, Long, Float, Double). There is no support for boolean/Boolean, Byte[], etc.

forClass

Another way to build a CursorMock instance is to use actual data classes.

public class Item {

    long id;
    String title;
    float rating;

    public Item(long id, String title, float rating) {
        this.id = id;
        this.title = title;
        this.rating = rating;
    }
}
final CursorMock mock = CursorMockBuilder.forClass(Item.class)
                .add(new Item(1L, "Item #1", 12))
                .add(new Item(2L, "Item #2", 36))
                .build();

Schema will be generated from a class definition. By default all transient and static fields are ignored and column name value is taken for Field name. Default behaviour also puts restrictions on using types.

ObjectProcessor

ObjectProcessor is used to:

  • filter class fields
  • define column name
  • obtain a value from a field
public interface ObjectProcessor {
    boolean ignore(Field field);
    String columnName(Field field);
    Object value(Field field, Object holder);
}

Please note that value methos should always return an object of supported type (listed above) or null.

For example we are using some ORM library and have specific annotation for a column:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Column {
    String value() default "";
}

If a Field is not annotated with it - we ignore it. It also has an optional value that we can use to change a name of the column.

public class Item {

    @Column("item_id")
    long id;

    @Column
    String name;

    @Column
    int score;

    float willBeIgnored;

    public Item(long id, String name, int score) {
        this.id = id;
        this.name = name;
        this.score = score;
    }
}

Our ObjectProcessor would look like (we can extend default ObjectProcessorImpl):

public class MyObjectProcessor extends CursorMockBuilder.ObjectProcessorImpl {

    @Override
    public boolean ignore(Field field) {
        return field.getAnnotation(Column.class) == null;
    }

    @NonNull
    @Override
    public String columnName(Field field) {
        final String out;
        final Column column = field.getAnnotation(Column.class);
        if (column == null
                || TextUtils.isEmpty(column.value())) {
            out = field.getName();
        } else {
            out = column.value();
        }
        return out;
    }
}

In order to build a Cursor based on an object's class definition that has some specific logic:

final CursorMock mock = CursorMockBuilder.forClass(Item.class, new MyObjectProcessor())
                .add(new Item(1L, "#1", 45))
                .add(new Item(2L, null, 89))
                .add(new Item(3L, "#3", -1))
                .build();

The data will be as follows:

* item_id name score
1 1L #1 45
2 2L null 89
3 3L #3 -1

License

  Copyright 2016 Dimitry Ivanov (mail@dimitryivanov.ru)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.