Is there a way to mock QSqlQuery?

1.9k views Asked by At

I just recently discovered gmock and am now in progress of "rethinking the whole programming process as it is", adding unit tests wherever I can. One thing that struck me as weird in the process is that QSql module clearly being an external dependency to our code, doesn't give developers tools to mock its internals. The best thing I can think of with this module is in-memory DB which is way harder to implement than a simple mock and is not even always possible (consider faking oracle packages with in-memory DB)

Now, for me, that is not exactly a problem, a while ago we have switched to home grown ocilib wrapper that inherits from virtual interface (and is, thus, easily mockable). But really, is there no way to mock when you use Qt's own QSql module? Or rather - Qt being a (really good) framework, do they really provide no automation for such use cases or am I missing something?

UPD1: A small update on the importance of the question:

My code is VERY heavily interleaved with Oracle SQL queries as, for certain, code of a lot of other people. Unit testing such a code is virtually impossible when an external dependency, which is also heavily in development, sometimes supplies incorrect data. When your unit test breaks, you want it to be your code that is at fault, not Oracle. Which is why I asked the original question. If there exists/existed a way to semi-easily mock the dependency using qsqlquery interface then writing unit tests for code using QSql becomes possible.

UPD2: Although, after further consideration, I have to admit that the problem could be avoided with better code design (OO instead of free functions at some places) and better entity separation. So, virtually impossible in UPD1 was not really justified. Though this doesn't really make original question less important. When you are tasked maintaining legacy code, for example, mocking QtSql is the only realistic way of introducing tests to the system.

2

There are 2 answers

0
grauer.hase On

Zeks, IMO you have 2 approaches to mock Qt Sql classes:

  1. Subclassing Qt Sql classes;
  2. Wrapper around the Qt Sql classes and passing them through interfaces.

Approach #1:

Generally it's pain. First, if you want to mock QSqlQuery you have to create subclasses for QSqlResult, QSqlDriver and QSqlQuery itself. Then, another pain comes into the game, you have to set preconditions - for example: you want your sql to return true on calling exec() function, for this purpose, your subclass of QSqlDriver has to return:

class QSqlDriverTest : public QSqlDriver
{
   ...
   virtual bool isOpen() const { return true; }
   virtual void setOpenError(bool e) { QSqlDriver::setOpenError(false); }
   ...
};

That's only one example. There are even more preconditions for calling next() function successfully. To find out them you always need to look into qt source code. So it's entirely depends on qt. This approach fails because:

  • it's not easy - unit testing has to be easy;
  • you still have qt dependency;

Approach #2:

I think it's the best and clear way to mock queries, but you need to prepare your code. You create an interface ISQLQuery which has the same functions as QSqlQuery (the same for QSqlDriver and QSqlResult). Like this:

class ISQLQuery   // interface wrapper for QSqlQuery
{
public:
   ~ISQLQuery(){}
   ...
   virtual bool exec() = 0;
   virtual QVariant value(int i) const = 0;
   virtual const ISQLDriver * driver() const = 0;
   ...
};

class ISQLDriver   // interface wrapper for QSqlDriver
{
public:
   ~ISQLDriver(){}
   ...
   virtual bool subscribeToNotification(const QString & name) = 0;
   ...
};

Then you create real implementations (that's just draft idea):

class SQLDriver : public ISQLDriver
{
public:
   SQLDriver(const QSqlDriver * driver) : mpDriver(driver){}
   ...
   virtual bool subscribeToNotification(const QString & name) 
      { return mpDriver->subscribeToNotification(name); }
   ...
private:
   const QSqlDriver * mpDriver;
};

class SQLQuery : public ISQLQuery
{
public:
   SQLQuery(): mDriver(mQuery->driver){}
   ...
   virtual bool exec() { return mQuery.exec(); }
   virtual QVariant value(int i) const { return mQuery.value(i); }
   virtual const SQLDriver * driver() const { return &mDriver; }
   ...
private:
   QSqlQuery mQuery;
   SQLDriver mDriver;
   ...
};

There is an example how to use new sql classes, when all interfaces are created and implemented:

// some function
{
   ...
   SQLQuery query = SQLFactory::createSQLQuery();   // here you can put your mocks
   query.prepare("DROP TABLE table_hell;");
   query.exec();
   ...
}

I've shown you the main idea without all details otherwise the post might become huge. I hope you'll find my explanation useful.

Regards.

16
jxh On

If you just want an in memory SQL database to use as a mock backend for QtSQL, you can consider using SQLite.

SQLite is an in-process library that implements a self-contained, serverless, zero-configuration, transactional SQL database engine. The code for SQLite is in the public domain and is thus free for use for any purpose, commercial or private. SQLite is the most widely deployed database in the world with more applications than we can count, including several high-profile projects.

The advantage of using a real SQL interpreter behind the QtSQL calls is that you can validate the SQL syntax passed in, and whether the query actually returns the expected outcome.

If your concern is testing SQL queries that exercise Oracle SQL specific features, then there is no other way to know you are using those features correctly without testing against a real Oracle SQL server.