I have a method. What is the best practice to unit testing?
You can make a mock Connection and ResultSet and pass as a method parameter as an object but I find it stupid, unprofessional solution.
public static void playA() {
MyObject object = check("cat");
// some stuff...
}
private static MyObject check(String s) throws SQLException {
// some stuff checking string
MyObject o = methodA(s);
// some stuff doing on the object
return o;
}
private static MyObject methodA(String s) throws SQLException {
Connection c = MySettings.getConnection();
PreparedStatement ps = c.prepareStatement("select * from Objects where type = ?");
ps.setString(1, s);
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()) {
// mapping
return object;
}
return null;
}
My take is that it should be according the level of abstraction you want to test. Mocking Connection and ResultSet can be fine if you want to check the the logic of your SQL for example.
If you want to be precise testing the database connection layer you can use tools for unit testing database like http://dbunit.sourceforge.net/
Also try to have your code as most Object Oriented as possible. that would help you in testing ( although I know it's probably just an example )
Related
I'm trying to convert from ResultSet to CachedRowSet/CachedRowSetImpl. The ResultSet seems to be empty after the populate method, but so does the CachedRowSet. I have been searching all over the place, trying different approaches (including Factory). Below is a code snippet with some indications of what is going on.
class ResultSetMapper implements RowMapper<CachedRowSet>{
#Override
public CachedRowSet map(ResultSet rs, StatementContext ctx) throws SQLException {
//CachedRowSet crs = RowSetProvider.newFactory().createCachedRowSet();
System.out.println(rs.getLong("something")); -> This gets printed
CachedRowSetImpl crs = new CachedRowSetImpl();
crs.populate(rs);
System.out.println(crs.getInt("something"); -> ArrayIndexOutOfBoundsException (mostly -1, sometimes returning 0)
System.out.println(rs.getLong("something")); -> This doesn't get printed
System.out.println(crs.size()); -> 0
return crs;
}
}
Any help or insight to this problem will be greatly appreciated!
EDIT: Through some debugging, I found that the CachedRowSet is not empty. The RowSetMD.colCount = 3. It also has the right labels. This doesn't change the issue, but assures that I'm not calling getters on an empty object. This makes the issue even harder to grasp
The CachedRowSet::populate method reads all rows from your ResultSet. At that point it is no longer possible to call rs.next(). You should use csr.next().
class ResultSetMapper implements RowMapper<CachedRowSet>{
#Override
public CachedRowSet map(ResultSet rs, StatementContext ctx) throws SQLException {
CachedRowSet crs = RowSetProvider.newFactory().createCachedRowSet();
crs.populate(rs);
while (csr.next()) {
System.out.println(crs.getInt("something"));
}
// ...
return null;
}
}
I am trying to test JDBC calls to Oracle DB with jMockit. I have tried to simulate JDBC's Connection#prepareStatement(String sql) to return PreparedStatement mock object, but I only get a null value instead. My aim is to mock Connection APIs to return a mock PreparedStatement object, and to mock PreparedStatement APIs to return a mock ResultSet object.
My source code is given below.
try (Connection conn = DriverManager.getConnection(url, username, password);) {
try(PreparedStatement ps = conn.prepareStatement(
"SELECT firstName, lastName from Employee where empId = ?");) {
ps.setString(1, empId); // This is an input to function.
try(ResultSet rs = ps.executeQuery();) {
while(rs.next()) {
Employee emp = new Employee();
emp.setFirstName(rs.getString("firstName"));
emp.setLastName(rs.getString("lastName"));
return emp;
}
}
}
}
return employees;
When I invoke
PreparedStatement ps = conn.prepareStatement(
"SELECT firstName, lastName from Employee where empId = ?")
My unit test is as follows
#Test()
public void testQueryOutOfDateSpanishContent_Success(
#Mocked final Connection connection, #Mocked final PreparedStatement ps) throws Exception {
new Expectations(DriverManager.class) {
{
DriverManager.getConnection(
dcrParameters.getEnvironmentUrl(), dcrParameters.getUsername(),
dcrParameters.getPassword());
result = connection;
connection.prepareStatement(anyString);
result = with(new Delegate<PreparedStatement>() {
public PreparedStatement prepareStatement(String sql) throws SQLException {
return ps;
}
});
}
};
// Call the source function here.
I am using TestNG 6.10 with latest version of jMockit release. I am running the unit test with TestNG eclipse plugin. I am passing -javaagent:C:\downloads\jmockit.jaras a VM argument in Eclipse.
Update: The method accepts two mocked arguments that are provided by jMockit. If I don't pass the java agent, TestNG throws an error expecting the arguments to be passed through TestNG's dataProvider functionality.
http://testng.org/doc/documentation-main.html#parameters-dataproviders
It does return a mock object, but it resorts to default behaviour. For example, I want to capture what value is being passed to ps.setString(1, empId) or to simulate rs.next() to return true. But only default outputs are returned.
It's not a bug. It's a feature :)
You need to configure the mock. By default they will return null.
You need to add 'Expectation'-s, as you can see on http://jmockit.org/tutorial/Mocking.html
I am searching for more hours now with no result. Please help...
This is my class to test:
public class DBSelectSchema extends Database {
private static final Logger LOG = Logger
.getLogger(DBSelectSchema.class.getName());
private Connection conn = null;
public DBSelectSchema() {
super();
}
/**
* This method will return the version of the database.
*
* #return version
* #throws Exception
*/
public JSONObject getVersionFromDB() throws SQLException {
ResultSet rs = null;
JSONObject version = new JSONObject();
PreparedStatement query = null;
try {
conn = mensaDB();
query = conn.prepareStatement("SELECT number FROM version");
rs = query.executeQuery();
if (rs.isBeforeFirst()) {
rs.next();
version.put(HTTP.HTTP, HTTP.OK);
version.put("version", rs.getString("number"));
} else {
version.put(HTTP.HTTP, HTTP.NO_CONTENT);
version.put(HTTP.ERROR, "Die SQL Abfrage lieferte kein Result!");
}
rs.close();
query.close();
conn.close();
} catch (SQLException sqlError) {
String message = ERROR.SQL_EXCEPTION;
LOG.log(Level.SEVERE, message, sqlError);
return version;
} catch (JSONException jsonError) {
String message = ERROR.JSON_EXCEPTION;
LOG.log(Level.SEVERE, message, jsonError);
return version;
}
return version;
}
I am trying to get in each branch for 100% code coverage.
How can I mock ResultSet rs, JSONObject version and PreparedStatement query to do/return what I want:
Currently I am testing like that:
#Test
public void getVersionFromDB_RS_FALSE() throws SQLException, JSONException {
MockitoAnnotations.initMocks(this);
Mockito.when(dbSelMocked.mensaDB()).thenReturn(conn);
Mockito.when(conn.prepareStatement(Mockito.anyString())).thenReturn(query);
Mockito.when(query.executeQuery()).thenReturn(rs);
Mockito.when(rs.isBeforeFirst()).thenReturn(false);
JSONObject returnObj = dbSelMocked.getVersionFromDB();
assert(...);
}
But this just works when the 3 variables are class variables (like Connection conn) and not local variables. But I dont want them (even Connection) not to be global.
=== EDIT 1 ===
It works like that if all variables are local:
#Test
public void getVersionFromDB_RS_FALSE() throws SQLException, JSONException {
System.out.println("####################");
System.out.println("started test: getVersionFromDB_RS_FALSE");
System.out.println("####################");
Connection conn = Mockito.mock(Connection.class);
PreparedStatement query = Mockito.mock(PreparedStatement.class);
ResultSet rs = Mockito.mock(ResultSet.class);
MockitoAnnotations.initMocks(this);
Mockito.when(dbSelMocked.mensaDB()).thenReturn(conn);
Mockito.when(conn.prepareStatement(Mockito.anyString())).thenReturn(query);
Mockito.when(query.executeQuery()).thenReturn(rs);
Mockito.when(rs.isBeforeFirst()).thenReturn(false);
JSONObject returnObj = dbSelMocked.getVersionFromDB();
assertTrue(returnObj.has("error"));
}
But I am not able to mock JSONObject version in another test anymore :(
How can I do that?
#Test
public void getVersionFromDB_JSON_EXCEPTION() throws SQLException, JSONException {
System.out.println("####################");
System.out.println("started test: getVersionFromDB_JSON_EXCEPTION");
System.out.println("####################");
JSONObject version = Mockito.mock(JSONObject.class);
MockitoAnnotations.initMocks(this);
doThrow(new JSONException("DBSelectSchemaIT THROWS JSONException")).when(version).put(anyString(), any());
JSONObject returnObj = dbSelMocked.getVersionFromDB();
System.out.println(returnObj.toString());
assertTrue(returnObj.equals(null));
}
I think its overwritten in the real method... because it does not throw an exception and the method does not fail.
Your test code has multiple issues.
The test is verbose and fragile
The same (verbose) setup is required for multiple tests
You don't test real object, instead you are using mock of your class for testing
The first 2 issues can be solved by extracting repeated code to a setup method (I added static import for Mockito to reduce the noise):
#Before
public void setUp() throws Exception {
Connection conn = mock(Connection.class);
PreparedStatement query = mock(PreparedStatement.class);
when(dbSelMocked.mensaDB()).thenReturn(conn);
when(conn.prepareStatement(anyString())).thenReturn(query);
when(query.executeQuery()).thenReturn(rs);
rs = mock(ResultSet.class); // rs is field
}
Now in each of your tests you can configure rs to return whatever you need:
#Test
public void getVersionFromDB_RS_FALSE() throws Exception {
// Given
when(rs.isBeforeFirst()).thenReturn(false);
// When
JSONObject returnObj = dbSelMocked.getVersionFromDB();
// Then
assertTrue(returnObj.has("error"));
}
Now the most important issue: you are mocking class DBSelectSchema to return connection mock. Mocking class under test can cause different hard-to-spot problems.
To solve this issue you have 3 options:
Refactor your code and inject some connection factory. So you'll be
able to mock it in your test.
Extend class DBSelectSchema in your test and override method
mensaDB() so it will return mocked connection
Use embedded database like H2 and put test data in 'number' table
before calling getVersionFromDB()
Option #1
Extract creation of connection to a separate class and use it in your DBSelectSchema:
public class ConnectionFactory {
public Connection getConnection() {
// here goes implementation of mensaDB()
}
}
Then inject it to your DBSelectSchema:
public DBSelectSchema(ConnectionFactory connFactory) {
this.connFactory = connFactory;
}
Now your test you can use real DBSelectSchema class with mocked ConnectionFactory
ConnectionFactory connFactory = mock(ConnectionFactory.class);
dbSel = new DBSelectSchema(connFactory);
Option #2
You can make almost real class under test:
final Connection conn = mock(Connection.class);
dbSel = new DBSelectSchema() {
#Override
public Connection mensaDB() {
return conn;
}
};
Option #3
This option is most preferable, because you will call real SQL commands and you mock the whole database instead of classes. It requires some effort to use plain JDBC here, but it worth that. Keep in mind that SQL dialect can differ from the DB used in production.
#Before
public void setUp() throws Exception {
Class.forName("org.h2.Driver");
conn = DriverManager.getConnection("jdbc:h2:mem:test;INIT=RUNSCRIPT FROM 'classpath:schema.sql'");
}
#After
public void tearDown() throws Exception {
conn.close();
}
Then in your test you simply add required records to DB:
#Test
public void getVersionFromDB() throws Exception {
// Given
conn.prepareStatement("INSERT INTO version(number) VALUES (1)").execute();
// When
JSONObject returnObj = dbSel.getVersionFromDB();
// Then
assert(...);
}
Obviously, DBSelectSchema must use the same connection, so you can use in combination with options #1 and #2,
You are unit testing data access layer by mocking all ADO calls. By doing so, you will end up with a unit test that does not really test any logic.
Taking an example from your code: assume that you are using the following sql to retrieve a version number : SELECT number FROM version. Now assume that the column name changed and you should retrieve 2 additional column from your sql. You will eventually end up with an sql like SELECT number, newColumn1, newColumn2 FROM version. With the test you would have written (using mock), it would still pass even though its not really testing whether the 2 new column is being retrieved. You get my point?
I would advise you to have a look at this thread for some possible alternative to test your data access layer. Using mock for your data access layer will end up with brittle test that does not really test anything
Your test is much too large, and you seem to be testing too much.
Split your code along it's natural breaks, so that the code that does the data retrieval is separate from the logic that manipulates it.
You only want to test the code that you write, not the 3rd party code. Its out of scope for your needs, and if you can't trust it, then don't use it.
I'm writing generic logger for SQLException and I'd like to get parameters that were passed into PreparedStatement, how to do it ? I was able to get the count of them.
ParameterMetaData metaData = query.getParameterMetaData();
parameterCount = metaData.getParameterCount();
Short answer: You can't.
Long answer: All JDBC drivers will keep the parameter values somewhere but there is no standard way to get them.
If you want to print them for debugging or similar purposes, you have several options:
Create a pass-through JDBC driver (use p6spy or log4jdbc as a basis) which keeps copies of the parameters and offers a public API to read them.
Use Java Reflection API (Field.setAccessible(true) is your friend) to read the private data structures of the JDBC drivers. That's my preferred approach. I have a factory which delegates to DB specific implementations that can decode the parameters and that allows me to read the parameters via getObject(int column).
File a bug report and ask that the exceptions are improved. Especially Oracle is really stingy when it comes to tell you what's wrong.
Solution 1: Subclass
Simply create a custom implementation of a PreparedStatement which delegates all calls to the original prepared statement, only adding callbacks in the setObject, etc. methods. Example:
public PreparedStatement prepareStatement(String sql) {
final PreparedStatement delegate = conn.prepareStatement(sql);
return new PreparedStatement() {
// TODO: much more methods to delegate
#Override
public void setString(int parameterIndex, String x) throws SQLException {
// TODO: remember value of X
delegate.setString(parameterIndex, x);
}
};
}
If you want to save parameters and get them later, there are many solutions, but I prefer creating a new class like ParameterAwarePreparedStatement which has the parameters in a map. The structure could be similar to this:
public class ParameterAwarePreparedStatement implements PreparedStatement {
private final PreparedStatement delegate;
private final Map<Integer,Object> parameters;
public ParameterAwarePreparedStatement(PreparedStatement delegate) {
this.delegate = delegate;
this.parameters = new HashMap<>();
}
public Map<Integer,Object> getParameters() {
return Collections.unmodifiableMap(parameters);
}
// TODO: many methods to delegate
#Override
public void setString(int parameterIndex, String x) throws SQLException {
delegate.setString(parameterIndex, x);
parameters.put(parameterIndex, x);
}
}
Solution 2: Dynamic proxy
This second solution is shorter, but seems more hacky.
You can create a dynamic proxy by calling a factory method on java.lang.reflect.Proxy and delegate all calls on the original instance. Example:
public PreparedStatement prepareStatement(String sql) {
final PreparedStatement ps = conn.prepareStatement(sql);
final PreparedStatement psProxy = (PreparedStatement) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{PreparedStatement.class}, new InvocationHandler() {
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("setLong")) {
// ... your code here ...
}
// this invokes the default call
return method.invoke(ps, args);
}
});
return psProxy;
}
Then you intercept the setObject, etc. calls by looking at method names and looking to the second method arguments for your values.
This article, from Boulder, ahtoulgh DB 2 "specific", gives a complete example of ParameterMetadata usage.
I'm trying to generate some sql files in my java application.
The application will not execute any sql statements, just generate a file with sql statements and save it.
I'd like to use the java.sql.PreparedStatement to create my statements so that i don't have to validate every string etc. with my own methods.
Is there a way to use the PreparedStatement without the calling java.sql.Connection.prepareStatement(String) function, because I don't have a java.sql.Connection?
Take a look at this Java library: http://openhms.sourceforge.net/sqlbuilder/
I'm guessing that until you've got a sql connection, the parser won't know what rules to apply. I'm guessing that it's actually the SQL driver or even server that's compiling the sql statement.
Assuming your sql is simple enough, then how about using a cheap connection, like, say a sqlite connection.
SQLite will create a new database on the fly if the database you're attempting to connect to does not exist.
public Connection connectToDatabase() {
// connect to the database (creates new if not found)
try {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection("jdbc:sqlite:mydatabase.db");
// initialise the tables if necessary
this.createDatabase(conn);
}
catch (java.lang.ClassNotFoundException e) {
System.out.println(e.getMessage());
}
catch (java.sql.SQLException e) {
System.out.println(e.getMessage());
}
return conn;
}
Not really. Preparing a statement in most cases means that it will be compiled by DBMS which is "hard" without connection.
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html
This is a dastardly devious problem, thankfully it's pretty easy to cope with:
public class PreparedStatementBuilder
{
private String sql; // the sql to be executed
public PreparedStatementBuilder(final String sql) { this.sql = sql; }
protected void preparePrepared(final PreparedStatement preparedStatement)
throws SQLException
{
// this virtual method lets us declare how, when we do generate our
// PreparedStatement, we want it to be setup.
// note that at the time this method is overridden, the
// PreparedStatement has not yet been created.
}
public PreparedStatement build(final Connection conn)
throws SQLException
{
// fetch the PreparedStatement
final PreparedStatement returnable = conn.prepareStatement(sql);
// perform our setup directives
preparePrepared(returnable);
return returnable;
}
}
To use, just write an anonymous class that overrides void preparePrepared(PreparedStatement):
final String sql = "SELECT * FROM FOO WHERE USER = ?";
PreparedStatementBuilder psBuilder = new PreparedStatementBuilder(sql){
#Override
protected void preparePrepared(PreparedStatement preparedStatement)
throws SQLException
{
preparedStatement.setString(1, "randal");
}};
return obtainResultSet(psBuilder);
Presto! You now have a way to work with a PreparedStatement without yet having built it. Here's an example showing the minimal boilerplate you'd otherwise have to copy paste to kingdom come, every time you wanted to write a different statement:
public ResultSet obtainResultSet(final PreparedStatementBuilder builder)
throws SQLException {
final Connection conn = this.connectionSource.getConnection();
try
{
// your "virtual" preparePrepared is called here, doing the work
// you've laid out for your PreparedStatement now that it's time
// to actually build it.
return builder.build(conn).executeQuery();
}
finally
{
try { conn.close(); }
catch (SQLException e) { log.error("f7u12!", e); }
}
}
You really really don't want to be copy pasting that everywhere, do you?
Try implementing PreparedStatement.
Example : class YourOwnClass implements PreparedStatement {
// 1. Do implement all the methods ,
2. Get the minimal logic to implement from OraclePreparedStatement(classes12.jar) or
sun.jdbc.odbc.JdbcOdbcCallableStatement
}