Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing creating an SQLite database using Spock and Robospock

spock-core:0.7-groovy-2.0
robospock:0.5.0
Android Studio 0.8.2
Fedora release 20 (Heisenbug)

This is the complete solution. Now it compiles and runs the unit test successfully, and the directory structure is the same as the preview edit. Please feel free to comment on anything that doesn't look right.

Edit Solution =====

build.gradle:

apply plugin: 'java'
apply plugin: 'groovy'

repositories {
    mavenCentral()

    maven {
        // Location of Android SDK for compiling otherwise get this error:
        /* Could not find com.android.support:support-v4:19.0.1.
           Required by:
           :testSQLite:unspecified > org.robospock:robospock:0.5.0 > org.robolectric:robolectric:2.3 */
        url "/home/steve/local/android-studio/sdk/extras/android/m2repository/"
    }
}

dependencies {
    // just compile so we can use the sqlite API
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    testCompile 'org.codehaus.groovy:groovy:2.3.+'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

SnapzClientTest.groovy:

package com.example.DataAccess

import com.example.DataAccess.SnapzAndroidDB

import org.robolectric.Robolectric
import pl.polidea.robospock.RoboSpecification

class SnapClientTest extends RoboSpecification {

    /* Create Sqlite database for Android */
    def 'Create a sqlite database for Android'() {
        setup:
        def androidDB = new SnapzAndroidDB(Robolectric.application)

        expect:
        androidDB != null
    }
}

SnapzAndroidDB.java, no change from the 5th August Edit

Edit 5 August ================

Basically, I am trying to create a JAR file that will be used in an Android app that will have the functionality of SQLite, so I can use this JAR file for many apps.

I have started from scratch and created a smaller application that is easier to bug fix. This is the directory structure, and there is only three files:

testSQLite/build.gradle
testSQLite/src/main/java/com/example/sqltest/SnapzAndroidDB.java
testSQLite/src/test/groovy/SnapzClientTest.groovy

build.gradle

apply plugin: 'java'
apply plugin: 'groovy'

repositories {
    mavenCentral()

    maven {
        // Location of Android SDK for compiling otherwise get this error:
        /* Could not find com.android.support:support-v4:19.0.1.
           Required by:
           :testSQLite:unspecified > org.robospock:robospock:0.5.0 > org.robolectric:robolectric:2.3 */
        url "/home/steve/local/android-studio/sdk/extras/android/m2repository/"
    }
}

dependencies {
    // Just compile so we can use the sqlite API
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    testCompile 'org.codehaus.groovy:groovy:2.3.+'
    testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

SnapzAndroidDB.java

package com.example.DataAccess;

import java.util.logging.ConsoleHandler;
import java.util.logging.SimpleFormatter;
import java.util.logging.Handler;
import java.util.logging.Logger;
import java.util.logging.Level;

import android.content.Context;
import android.content.ContentValues;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteException;
import android.database.Cursor;

public class SnapzAndroidDB extends SQLiteOpenHelper {
    /**
     * Logger for displaying log messages
     */
    private static final Logger log = Logger.getLogger("SnapzAndroidDB");

    private SQLiteDatabase mDb;

    public SnapzAndroidDB(Context context) {
        super(context, "DB_NAME", null, 1);

        /* Create logger */
        ConsoleHandler consoleHandler = new ConsoleHandler();
        log.addHandler(consoleHandler);
        log.setLevel(Level.FINE);
        consoleHandler.setFormatter(new SimpleFormatter());
        consoleHandler.setLevel(Level.ALL);

        log.log(Level.INFO, "SnapzAndroidDB()");
    }

    /* Called only once first time the database is created */
    @Override
    public void onCreate(SQLiteDatabase mDb) {
        log.log(Level.INFO, "onCreate(SQLiteDatabase db)");

        String createConfig = String.format("create table %s (%s int primary key, %s text, %s text)",
                                         "TABLE_CONFIG",
                                         "ID",
                                         "NAME",
                                         "VALUE");

        log.log(Level.INFO, "onCreate with SQL: " + createConfig);
        mDb.execSQL(createConfig);
    }

    @Override
    public void onUpgrade(SQLiteDatabase mDb, int oldVersion, int newVersion) {
        log.log(Level.INFO, "onUpgrade()");
        /* Only if there is some schema changes to the database */
    }
}

SnapzClientTest.groovy

package com.example.DataAccess

import com.example.DataAccess.SnapzAndroidDB

import spock.lang.Specification
import org.robolectric.Robolectric

class SnapClientTest extends Specification {


    /* Create SQLite database for Android */
    def 'Create an SQLite database for Android'() {
        setup:
        def androidDB = new SnapzAndroidDB(Robolectric.application)

        expect:
        androidDB != null
    }
}

The error I am still getting is the following:

com.example.DataAccess.SnapClientTest > Create an SQLite database for Android FAILED
    java.lang.RuntimeException: Stub!
        at android.database.sqlite.SQLiteOpenHelper.<init>(SQLiteOpenHelper.java:4)
        at com.example.DataAccess.SnapzAndroidDB.<init>(SnapzAndroidDB.java:26)
        at com.example.DataAccess.SnapClientTest.Create a sqlite database for Android(SnapzClientTest.groovy:15)

Edit 4 August ===================

This is my updated test specification that uses Robolectric to generate a context that can be used in the constructor of SQLiteOpenHelper(...)

import org.robolectric.Robolectric

def 'Create an SQLite database for Android'() {
    setup:
    def androidDB = new SnapzAndroidDB(Robolectric.application)

    expect:
    androidDB != null
}

The function I am actually testing is a class that extends SQLiteOpenHelper. And my constructor SnapzAndroidDB(...) calls SQLiteOpenHelper() constructor, as you can see the context is the first parameter that is passed from the test spec:

public class SnapzAndroidDB extends SQLiteOpenHelper
    public SnapzAndroidDB(Context context) {
        super(context, SnapzContract.DB_NAME, null, SnapzContract.DB_VERSION);       
    }
    .
    .
}

When I run my test I get this error:

com.sunsystem.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
    java.lang.RuntimeException: Stub!
        at android.database.sqlite.SQLiteOpenHelper.<init>(SQLiteOpenHelper.java:4)
        at com.sunsystem.DataAccess.SnapzAndroidDB.<init>(SnapzAndroidDB.java:33)
        at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:168)

END EDIT =======================

Edit ====

When I try and use the getBaseContext() I get the following error:

com.sunsystem.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
    groovy.lang.MissingMethodException: No signature of method: com.sunsystem.HttpSnapClient.SnapClientTest.getBaseContext() is applicable for argument types: () values: []
        at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:159)

My specification spock function is this:

def 'Create an SQLite database for Android'() {
    setup:
    def androidDB = new SnapzAndroidDB(getBaseContext())

    expect:
    androidDB != null
}

Here are the dependencies:

dependencies {
    compile "com.googlecode.json-simple:json-simple:1.1.1", {
        // Exclude junit as we don't want this include in our JAR file as it will add hamcast and other dependencies as well
        exclude group:'junit', module: 'junit'
    }

    // Just compile so we can use the SQLite API. This won't be included in the JAR
    compile 'com.google.android:android:4.1.1.4', {
        // Do not bring in dependencies
        transitive = false
    }

    // Compile for unit testing only
    testCompile "org.codehaus.groovy:groovy:2.3.4"
    testCompile "org.spockframework:spock-core:0.7-groovy-2.0"
    testCompile 'org.robospock:robospock:0.5.0'
    testCompile 'com.google.android:android-test:4.1.1.4'
    testCompile 'com.android.tools.build:gradle:0.12.2'
    testCompile 'org.robospock:robospock-plugin:0.4.0'
}

====

I am doing Spock unit testing for my library written in Java that will be used in my Android application.

The Java JAR file that will be deployed to an Android application for doing database stuff. It's this JAR file I am testing.

I have written a Spock specification for testing the creation of an SQLite database.

In my Java JAR file I have a class that creates the SQLite database, and I want to test that in my Spock unit test.

However, the problem is that the SQLiteOpenHelper constructor needs to be called with a Context, and I am trying to mock that context using import android.text.mock.MockContext in my Spock unit test.

public class SnapzAndroidDB extends SQLiteOpenHelper implements SnapzDAO {
    public SnapzAndroidDB(Context context) {
        super(context, SnapzContract.DB_NAME, null, SnapzContract.DB_VERSION);    
    }

    /* Called only once first time the database is created */
    @Override
    public void onCreate(SQLiteDatabase db) {
        String sqlCreate = String.format("create table %s (%s int primary key, %s text, %s text, %s text)",
                                         SnapzContract.TABLE,
                                         SnapzContract.GetConfigColumn.ID,
                                         SnapzContract.GetConfigColumn.NAME,
                                         SnapzContract.GetConfigColumn.VALUE,
                                         SnapzContract.GetConfigColumn.CFG_TYPE);
        db.execSQL(sqlCreate);
    }
    .
    .
}

Now in my unit testing spec I have this in my SnapClientTest.groovy:

import android.test.mock.MockContext

def 'Create an SQLite database for Android'() {
    setup:
    def context = new MockContext()
    def androidDB = new SnapzAndroidDB(context.getApplicationContext())

    expect:
    androidDB != null
}

From this you can see that I am mocking the context and sending that as a parameter to the constructor of my class that will call the SQLiteOpenHelper constructor.

The error I get when I run my unit test is this:

com.HttpSnapClient.SnapClientTest > Create an SQLite database for Android FAILED
11:05:27.062 [DEBUG] [TestEventLogger]     java.lang.RuntimeException: Stub!
11:05:27.063 [DEBUG] [TestEventLogger]         at android.content.Context.<init>(Context.java:4)
11:05:27.063 [DEBUG] [TestEventLogger]         at android.test.mock.MockContext.<init>(MockContext.java:5)
11:05:27.063 [DEBUG] [TestEventLogger]         at com.sunsystem.HttpSnapClient.SnapClientTest.Create a sqlite database for Android(SnapClientTest.groovy:155)
11:05:27.065 [QUIET] [system.out] 11:05:27.064 [DEBUG] [org.gradle.process.internal.child.ActionExecutionWorker] Stopping client connection.

Being new to Spock I am not sure if this is possible or not, as I am just testing my JAR file.

like image 534
ant2009 Avatar asked Jul 23 '14 04:07

ant2009


1 Answers

Spock is one of the most widely used frameworks in the Groovy and Java ecosystem that allows the creation of BDD tests in a very intuitive language and facilitates some common tasks such as mocking and extensibility. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. To work with Spock you basically need to perform a set of steps, such as following a recipe, that will allow you to effectively implement both a unit test and a web integration.

Your current error message reads:

Create a sqlite database for Android FAILED

Try these steps and see how it goes:

Including to your code getWritableDatabase and getReadableDatabase should help:

jokesHelper dbHelper = new jokesHelper(getBaseContext());
SQLiteDatabase db = dbHelper.getWritableDatabase();

Doing so, Android will be able manage and cache the connection.

Yet if you got any error message from getBaseContext, try to uninstall the testing plugin and recreate the STS resources (.classpath & .project) using integrate-with --eclipse, then it should work.

If you are having any problem with getSpecificationContext, it means that some detail was left out and you need to double check your specifications.

In case you are not using Eclipse, to create your Java Jar file with spock, you can interface it with Emacs through command-line Java development tools as usual, for instance Sun’s JDK or any other approach expected for Enterprise Development. To only run a SampleTest you must invoke the test task from the command-line with the Java system property:

gradle -Dtest.single=Sample test

Or alternatively

gradle -Dtest.single=SoapTest clean test

Also check what are the permissions on the directory in use. And in case you haven't done yet, remember to include dependencies:

dependencies {
    classpath 'com.android.tools.build:gradle:0.8.+'
    classpath 'org.robospock:robospock-plugin:0.4.0'
}

And inform the test directory that you are using, e.g. srcDirs. And keep in mind that ("Es ist wichtig das man hier die richtige Resources-Klasse importiert") it is important to import the right resource required by the class. As such, also include to "build.gradle" in the "defaultConfig":

testPackageName "com.yourpackage.test"
testInstrumentationRunner "android.test.InstrumentationTestRunner"
testFunctionalTest true

The Spock and Robospock are innovative tools that can aid helpful resources for development of unit test. Alternatively you could also use tools such as TCL Tests. The TCL Tests are the oldest set of tests for SQLite and is the best approach you can take. In fact, SQLite started life as a Tcl extension. Much of the testing and development tools for SQLite are written in Tcl. In addition to the native C API, the Tcl extension is the only API supported by the core SQLite team.

To enable the Tcl bindings, download the TEA (Tcl Extension Architecture) distribution of the SQLite source from the SQLite website. This version of the code is essentially the amalgamation distribution with the Tcl bindings appended to the end. This will build into a Tcl extension that can then be imported into any Tcl environment.

enter image description here

Very specific steps should be followed and attention to every single detail is essential, as it can make the difference to enable your testing to successfully run or not.

The instrumentation framework is the foundation of the testing framework. Instrumentation controls the application under test and permits the injection of mock components required by the application to run. For example, you can create mock Contexts before the application starts and let the application use them.

enter image description here

All interaction of the application with the surrounding environment can be controlled using this approach. You can also isolate your application in a restricted environment to be able to predict the results, forcing the values returned by some methods or mocking persistent and unchanged data for ContentProvider, databases, or even the filesystem content. Therefore, it is also important to specify in your activity the information that you are running a test:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.aatg.sample.test"
        android:versionCode="1" android:versionName="1.0">
        <application android:icon="@drawable/icon"
            android:label="@string/app_name">
            <uses-library android:name="android.test.runner" />
        </application>
        <uses-sdk android:minSdkVersion="7" />
        <instrumentation
            android:targetPackage="com.example.aatg.sample
            android:name="android.test.InstrumentationTestRunner"
            android:label="Sample Tests" />
        <uses-permission android:name="
            android.permission.INJECT_EVENTS" />
    </manifest>

In case you run JNI to manipulate your database with native code, there are two ways to load an extension with SQLite. One is through a C API call, and one is through an SQL function that calls down into the same code as the C API function. In both cases, you provide a filename and, optionally, the name of the entry point function:

int sqlite3_load_extension( sqlite3 *db, const char *ext_name,
    const char *entry_point, char **error )

The other way to load a loadable extension is with the built-in SQL function:

load_extension( 'ext_name' )
load_extension( 'ext_name', 'entry_point' )

This function is similar to the C sqlite3_load_extension() call, with one major limitation. Because this is an SQL function, when it is called there will be, by definition, an SQL statement executing when the extension is loaded. That means that any extension loaded with the load_extension() SQL function will be completely unable to redefine or delete a custom function, including the specialized set of like() functions. Approaches to load the data with suitable syntax works similarly with Java, as expected.

enter image description here

Debug directives are only used for testing and development purposes, as they add significant overhead and make everything run noticeably slower, just similarly as to including throws Exception. As you are running unit test you need to set them accordingly, and also check to avoid that you database don't get corrupted. Basically, achieving the best tune for your debugging settings will improve and help you go smoothly to put your testing to run in the best way.

In addition to all the other build directives, SQLite has a fair number of SQLITE_OMIT_* compile-time directives. These are designed to remove core features from the build in an effort to make the core database library as small and compact as possible. In order to use most of these omit directives, you need to be building SQLite from the development sources found in the source control tree. Most omit directives won’t work correctly when applied to a source distribution or to the pre-built amalgamation. Also be aware that these compile-time directives are not officially supported, in the sense that they are not part of the official testing chain. For any given version of SQLite, there may be both compile problems and runtime issues if arbitrary sets of omit flags are enabled.

enter image description here

Of course you don't need to be a samurai to run unit test for SQLite on Android, although it might help.

like image 105
Avanz Avatar answered Oct 02 '22 04:10

Avanz