Is there a way to mock QSqlQuery?
Asked Answered
N

2

19

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.

Nobility answered 18/6, 2015 at 0:16 Comment(9)
You can try implement own fake sql driver, where you can implement verify functions.Monarchal
well, that's not very user friendly, isn't it?:) I'd expect fake driver already to be in place for ppl to reuseNobility
What's worse, if you want to fake a driver you will inevitably have to learn exactly waht, when and how is being called from qsqlquery, because otehrwise you won't be able to properly fake calls to itNobility
I mean, faking a driver is essentially what I'd have done if it was absolutely necessary, but that'd be highly complicated, lengthy process, requiring me to delve deep into _p files of qt installation. In short - exactly the opposite of why frameworks exist. I just thought that, maybe, Qt ppl thought of such use case and there are some mechanisms in place to avoid all this hassleNobility
I think, not exist other ways for mocking qsqlquery.Monarchal
I think it would be better to have a real Oracle SQL server available to test your queries against.Cherenkov
jxh, you fail completely to understand what a mock is and what is its purpose. The very purpose of a mock is to take out dependency and test the code as if it was operating correctlyNobility
I want to supply QSqlQuery with a range of values that it will return from its functions, so that my tests do not depend on an Oracle Databse that may or may not be in a correct state. This uncertainty of the state of the DB makes any testing of function code impossible. Mocks solve that problem by replacing the dependency with a completely fake object much simpler than even in memory DB (see 'google mock')Nobility
I am speaking from experience because I have the exact thing I am describing implemented, just with my own sql driver. My driver supports easy creation of mock objects via virtual interface and PIMPL infrastructure. But the main question of this thread is if Qt has functionality to do that or not. Or if there is a workaround that wont require to reimplement the driver. or if there is already fake driver coded somehwere...Nobility
F
2

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.

Fictionist answered 29/7, 2015 at 8:46 Comment(0)
C
1

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.

Cherenkov answered 27/6, 2015 at 1:24 Comment(16)
I do not want to test oracle specific features. What you are describing is a fake, not a mock. I want to test my functions without any kind of fake plugged in, with much simpler mock objects allowing me to not event tjink about what I am connecting toNobility
Regardless, you don't want to mock a correct response to an incorrect SQL query, so it is better to use a real SQL interpreter.Cherenkov
I do not want to mock a response to incorrect query. I want to mock a query so that I am sure the surrounding code works as intended. it is a completely different purposeNobility
It sounds like you want to implement unit tests against QtSQL. Do you not trust their own unit tests? Otherwise, your unit test code can just be compiled against an alternative QSqlQuery (not Qt's) that you mock up.Cherenkov
yes, I want to implement unit tests against qtsql code. if your app uses qtsql it is essentially what you NEED to do to write unit tests. I do not CARE about their own QtSql unit tests because that is completely different from what I need. I do not want to test if qtsql works as intended because it is not the wrapper module that introduces instability - it is external database that may or may not be brokenNobility
And I want to test my own code as if qtsql AND database were working correctly which is essentially the very purpose of mock objectsNobility
When you write unit test you want to be sure that if a test breaks, it is because your code is broken, not because external dependency is supplying incorrect data just this very moment. the latter is integration testing material, not unit testingNobility
And yes, I could of course use a fake, as you suggested in your answer, but fakes are by nature much harder to setup and maintain than mocks. That is why my original question was about mocking qsql, not faking itNobility
Users of the interface are passing in SQL code. Isn't it just as important to make sure you are passing in correct SQL code? Integrating SQLite into the unit test infrastructure is a one time cost for the gain of testing new queries or sequence of queries.Cherenkov
Passing correct sql code is task ortogonal to testing of function code and entirely out of the scope of this question. All of sql code is being tested at integration level anywayNobility
I have to repeat again - do not try to sell a fake as a mock. They are inherently dfferent. SQLite is nice but in no way an answer to the original questionNobility
While I commend your desire to keep the mock interface minimal, there are other issues to consider. For me, the unit test infrastructure must be easy to implement, and be able to test a large portion of the functionality. Using a real embedded SQL engine covers both, so I don't see a real downside to doing it.Cherenkov
except, at least in my case, sql means lots of calls to oracle packagesNobility
I agree that having a fake is a better solution, but the question was about mocking for a reason. Fake is simply not always possibleNobility
In fact, if you look at 'how google tests its software' book, they clearly state that for small sized tests all external interfaces must be mocked, not faked. And I completely agree with this desicionNobility
I don't disagree with the principle that external interfaces be mocked. But if mocking requires writing more code than incorporating an existing library, I will opt to incorporate the small library. YMMV.Cherenkov

© 2022 - 2024 — McMap. All rights reserved.