Available in versions: Dev (3.20) | Latest (3.19) | 3.18 | 3.17 | 3.16 | 3.15 | 3.14 | 3.13 | 3.12 | 3.11 | 3.10
Mocking Connection
Applies to ✅ Open Source Edition ✅ Express Edition ✅ Professional Edition ✅ Enterprise Edition
When writing unit tests for your data access layer, you have probably used some generic mocking tool offered by popular providers like Mockito, jmock, mockrunner, or even DBUnit. With jOOQ, you can take advantage of the built-in JDBC mock API that allows you to emulate a simple database on the JDBC level for precisely those SQL/JDBC use cases supported by jOOQ.
Disclaimer: The general idea of mocking a JDBC connection with this jOOQ API is to provide quick workarounds, injection points, etc. using a very simple JDBC abstraction. It is NOT RECOMMENDED to emulate an entire database (including complex state transitions, transactions, locking, etc.) using this mock API. Once you have this requirement, please consider using an actual database product instead for integration testing, rather than implementing your test database inside of a MockDataProvider
.
Mocking the JDBC API
JDBC is a very complex API. It takes a lot of time to write a useful and correct mock implementation, implementing at least these interfaces:
-
java.sql.Connection
-
java.sql.Statement
-
java.sql.PreparedStatement
-
java.sql.CallableStatement
-
java.sql.ResultSet
-
java.sql.ResultSetMetaData
Optionally, you may even want to implement interfaces, such as java.sql.Array
, java.sql.Blob
, java.sql.Clob
, and many others. In addition to the above, you might need to find a way to simultaneously support incompatible JDBC minor versions, such as 4.0, 4.1
Using jOOQ's own mock API
This work is greatly simplified, when using jOOQ's own mock API. The org.jooq.tools.jdbc
package contains all the essential implementations for both JDBC 4.0 and 4.1, which are needed to mock JDBC for jOOQ. In order to write mock tests, provide the jOOQ Configuration with a MockConnection
, and implement the MockDataProvider
:
// Initialise your data provider (implementation further down): MockDataProvider provider = new MyProvider(); MockConnection connection = new MockConnection(provider); // Pass the mock connection to a jOOQ DSLContext: DSLContext create = DSL.using(connection, SQLDialect.ORACLE); // Execute queries transparently, with the above DSLContext: Result<BookRecord> result = create.selectFrom(BOOK).where(BOOK.ID.eq(5)).fetch();
As you can see, the configuration setup is simple. Now, the MockDataProvider
acts as your single point of contact with JDBC / jOOQ. It unifies any of these execution modes, transparently:
- Statements without results
- Statements without results but with generated keys
- Statements with results
- Statements with several results
- Batch statements with single queries and multiple bind value sets
- Batch statements with multiple queries and no bind values
The above are the execution modes supported by jOOQ. Whether you're using any of jOOQ's various fetching modes (e.g. pojo fetching, lazy fetching, many fetching, later fetching) is irrelevant, as those modes are all built on top of the standard JDBC API.
Implementing MockDataProvider
Now, here's how to implement MockDataProvider
:
public class MyProvider implements MockDataProvider { @Override public MockResult[] execute(MockExecuteContext ctx) throws SQLException { // You might need a DSLContext to create org.jooq.Result and org.jooq.Record objects DSLContext create = DSL.using(SQLDialect.ORACLE); MockResult[] mock = new MockResult[1]; // The execute context contains SQL string(s), bind values, and other meta-data String sql = ctx.sql(); // Exceptions are propagated through the JDBC and jOOQ APIs if (sql.toUpperCase().startsWith("DROP")) { throw new SQLException("Statement not supported: " + sql); } // You decide, whether any given statement returns results, and how many else if (sql.toUpperCase().startsWith("SELECT")) { // Always return one record Result<Record2<Integer, String>> result = create.newResult(AUTHOR.ID, AUTHOR.LAST_NAME); result.add(create .newRecord(AUTHOR.ID, AUTHOR.LAST_NAME) .values(1, "Orwell")); mock[0] = new MockResult(1, result); } // You can detect batch statements easily else if (ctx.batch()) { // [...] } return mock; } }
Essentially, the MockExecuteContext
contains all the necessary information for you to decide, what kind of data you should return. The MockResult
wraps up two pieces of information:
-
Statement.getUpdateCount()
: The number of affected rows -
Statement.getResultSet()
: The result set
You should return as many MockResult
objects as there were query executions (in batch mode) or results (in fetch-many mode). Instead of an awkward java.sql.ResultSet
, however, you can construct a "friendlier" org.jooq.Result
with your own record types. The jOOQ mock API will use meta data provided with this Result
in order to create the necessary JDBC java.sql.ResultSetMetaData
See the MockDataProvider Javadoc
for a list of rules that you should follow.
Feedback
Do you have any feedback about this page? We'd love to hear it!